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

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




前言

相关介绍

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能够捕捉从浅层语法特征到深层语义特征的不同级别的语言信息。
  2. 微调阶段

    • 在预训练完成后,BERT模型可以通过添加任务特定的输出层来进行微调,以适应不同的NLP任务,如情感分析、问答、命名实体识别等。
    • 微调过程利用了预训练阶段学到的语言表征,使得模型能够快速适应新的任务并取得优异的性能。

优点

  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架构的预训练语言模型,在自然语言处理领域具有广泛的应用前景和优势。然而,其计算资源需求高、训练数据需求大、长文本处理效果有限等缺点也需要注意。在实际应用中,需要根据具体任务和资源情况选择合适的模型和方法。

前提条件

实验环境

bash 复制代码
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进行标题分类

准备数据集

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

读取数据集

python 复制代码
# 定义两个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)

划分数据集

python 复制代码
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)

设置相关参数

python 复制代码
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个样本来计算损失和梯度。较大的批大小可以加速训练,但也可能增加内存消耗并影响模型的泛化能力。
  2. data_workers = 0:

    • 数据加载工作线程数(Data Workers)是指用于并行加载数据的线程数量。设置为0意味着数据加载将在主线程上同步进行,这可能会降低数据加载的速度。通常,增加工作线程数可以加速数据加载过程,但过多的线程可能会增加系统开销。
  3. learning_rate = 0.0001:

    • 学习率(Learning Rate)是控制模型参数更新幅度的超参数。较小的学习率意味着参数更新的步长较小,训练过程可能更稳定但收敛速度较慢;较大的学习率可能导致训练过程不稳定甚至发散。这里设置为0.0001是一个相对较小的值,适用于一些精细调整的场景。
  4. gradient_accumulation_steps = 1:

    • 梯度累积步数(Gradient Accumulation Steps)是指在更新模型参数前,累积梯量的次数。设置为1意味着每次迭代都会立即更新模型参数。在内存有限但希望使用较大批大小进行训练时,可以通过增加梯度累积步数来模拟较大的批大小。
  5. max_train_epochs = 30:

    • 最大训练轮数(Max Training Epochs)是指整个训练数据集被遍历的次数。一个epoch等于整个数据集通过模型一次。这里设置为30,意味着整个数据集将被遍历30次。
  6. warmup_proportion = 0.05:

    • 预热比例(Warmup Proportion)是指在训练初期,学习率逐渐增加所占整个训练过程的比例。预热可以帮助模型在训练初期更稳定地更新参数,避免由于初始学习率过高而导致的训练不稳定。这里设置为0.05,意味着在前5%的训练轮数中,学习率会逐渐增加。
  7. weight_decay = 0.01:

    • 权重衰减(Weight Decay)是一种正则化技术,用于防止模型过拟合。它通过向损失函数添加一个与模型参数平方成正比的项来实现,鼓励模型参数保持较小值。这里设置为0.01。
  8. max_grad_norm = 1.0:

    • 最大梯度范数(Max Grad Norm)是梯度裁剪(Gradient Clipping)的一种形式,用于控制梯度的最大值。如果梯度的范数超过这个值,梯度将被缩放以确保其范数不超过这个值。这有助于防止梯度爆炸问题。这里设置为1.0。
  9. cur_time = time.strftime("%Y-%m-%d_%H:%M:%S"):

    • 这行代码用于获取当前时间,并将其格式化为字符串,通常用于生成具有时间戳的文件名或日志,以便记录训练过程。
  10. device = torch.device('cuda'):

    • 这行代码指定了模型和数据应该在哪种设备上运行。'cuda'表示使用NVIDIA的CUDA技术来加速计算,通常是在具有NVIDIA GPU的计算机上。如果系统中没有可用的CUDA设备,PyTorch将回退到CPU。

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

创建自己DataSet对象

python 复制代码
# 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,
)

计算准确率

python 复制代码
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

定义预训练模型

python 复制代码
# 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)

定义优化器

python 复制代码
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)

训练模型

python 复制代码
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()
bash 复制代码
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比较 招聘信息

保存模型

python 复制代码
torch.save(model.state_dict(), 'bert_model_parameter.pkl')
torch.save(model, 'bert_model.pkl')

测试模型

python 复制代码
model = torch.load('bert_model.pkl')
model.load_state_dict(torch.load('bert_model_parameter.pkl'))

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

参考文献

1 论文地址https://arxiv.org/abs/1810.04805

2 开源代码https://paperswithcode.com/method/bert

3 https://huggingface.co/docs/transformers/model_doc/bert

相关推荐
奔跑中的小象3 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南3 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang3 小时前
LangGraph 状态机实战
人工智能
刹那芳华19923 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作3 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门3 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜4 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟4 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦4 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生4 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能