【毕业设计】基于 Bert 改进的情感分类

前言

Gitee 源码地址: https://gitee.com/futurelqh/deep-learning

一、环境配置

创建环境: conda create -n Bert python=3.8

进入虚拟环境: conda activate Bert

pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple

pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple

pip install scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple

由于 torch 直接用 pip 下载会报错,在官网先下载 torch 并安装: https://download.pytorch.org/whl/torch_stable.html

# 安装 torch(这步我没有操作,直接 pip 安装的 torchvision)
pip install torch-1.5.0-cp37-cp37m-win_amd64.whl
pip install torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple

pip install transformers -i https://pypi.tuna.tsinghua.edu.cn/simple

二、Bert 预训练模型官网下载

bert 预训练模型的下载有许多方式,比如从 github 官网上下载(官网下载的是 tensorflow 版本的),还可以从源码中找到下载链接,然后手动下载,最后还可以从 huggingface 中下载。

点进huggingface是这样的:

如果想下载 bert-base-chinese 预训练模型,可以在搜索框中搜索:

点击箭头所指位置,来到下载页面:

补充,好多找不到下载位置,见下图:(实际上只需要下载参数文件: pytorch_model.bin、词索引文件 vocab.txt、模型参数配置文件:config.json 即可)

pytorch、tensorflow、rust 三种语言的预训练模型,选择自己需要的下载即可,其他的还有配置文件,以及词库文件等等。

使用方法,以 pytorch 为例:

将下载好的文件放到一个目录里面,比如 bert-base-chinese ,然后用以下代码测试:

from transformers import BertModel,BertTokenizer

BERT_PATH = './bert-base-uncased'

tokenizer = BertTokenizer.from_pretrained(BERT_PATH)

print(tokenizer.tokenize('I have a good time, thank you.'))

bert = BertModel.from_pretrained(BERT_PATH)

print('load bert model over')
['i', 'have', 'a', 'good', 'time', ',', 'thank', 'you', '.']
load bert model over

三、代码内容讲解

代码整体流程:

  1. 数据读取、处理(设置批大小) → \to 模型配置与加载 定义优化器 → \to 定义损失函数 → \to 设置学习策略,进行模型训练

改进策略:

  1. 模型改进策略:Bert + BiLSTM + BiGRU (code/utils/model_utils.py:XFBert)
  2. loss 函数:PriorMultiLabelSoftMarginLoss、MultiFocalLoss、MultiDSCLoss
  3. 优化器策略:由传统的 adam 换为余弦退火
  4. 对抗训练:FGM
  5. 模型融合:多模型投票,硬投票/软投票
  6. 数据增强:无监督simcse (用训练集测试集所有样本,加载bert模型,先进行一个无监督simcse,然后加载simcse后训练的向量, 解决 Bert 向量稀疏的问题,传统的数据增强方式 eda 策略:随机替换词,随机删除,随机插入,随机删除/插入标点)
  7. 梯度累计
  8. 利用多个模型进行软硬投票,soft、hard

1. data/text_process.ipynb 查看数据集形式,分布等

目的: 读取数据集,查看数据分布,便于设置输入 Bert 时的序列长度,(Bert 本身的限制为不超过 512)

2. code/main_net.py 模型训练

此处首先需要按照第二章的讲解,下载相关预训练文件

MODEL_DIR = {
    "bert-base-wwm": r"../pretrained/bert-base-chinese",
}

加载 Bert 分词器 tokenizer,利用 Bert 模型按字分词,将文本划分为每个字

tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR[args.MODEL_NAME])

制作数据集,详细过程见 3.

test_valid_ds = Tags_dataset(data=test,
                                 tokenizer=tokenizer,
                                 max_seq_len=args.max_seq_length,
                                 prediction=True)

将数据集按批划分

test_sampler = SequentialSampler(test_valid_ds)
test_dataloader = DataLoader(test_valid_ds,
                                 batch_size=args.test_batch_size,
                                 sampler=test_sampler,
                                 drop_last=False,
                                 pin_memory=True)

运用 K 折交叉验证

# 交叉验证
    kf = KFold(n_splits=6, shuffle=True, random_state=args.seed)

交叉验证后返回了划分的训练集与验证集的索引,读取索引对应的数据

intent_train_dataset = train_data.loc[train_idx, :]
intent_validation_dataset = train_data.loc[val_idx, :]

将交叉验证划分的数据集进行处理,并划分批次

intent_train_ds = Tags_dataset(data=intent_train_dataset,tokenizer=tokenizer,max_seq_len=args.max_seq_length,intent2id=intent2id)
intent_valid_ds = Tags_dataset(data=intent_validation_dataset,tokenizer=tokenizer,max_seq_len=args.max_seq_length,intent2id=intent2id)


train_sampler = RandomSampler(intent_train_ds, replacement=False)
val_sampler = SequentialSampler(intent_valid_ds)


train_loader = DataLoader(intent_train_ds,batch_size=args.train_batch_size,sampler=train_sampler,drop_last=False,pin_memory=True)
val_dataloader = DataLoader(intent_valid_ds,batch_size=args.valid_batch_size,sampler=val_sampler,drop_last=False,pin_memory=True)

加载模型,详细见 4.

model = XFBert(MODEL_DIR[args.MODEL_NAME], intent_dim=len(intent2id), enable_mdrop=args.enable_mdrop)

设置优化器与学习策略,详细见 5.

# optimizer,scheduler
optimizer,scheduler = get_optimizer_and_schedule(args,model,trainloader_shape=len(intent_train_dataset))

执行模型训练 do_train,详细见 7.

best_score = do_train(args, model, train_loader, val_dataloader, device, intent2id, optimizer, scheduler)

# 保存当前划分集合下的最好结果
kf_results.append({"kfold": kfidx, "best_intent": best_score})

进行预测,详细见 8.

predict_intent_prob, predict_intent = do_sample_predict(model,test_dataloader,device,is_prob=True)

训练多个模型,执行 hard 时为硬投票,即通过每个模型预测的标签进行投票觉得,执行 soft 时为软投票,即通过对每个模型预测的概率值来进行选择最终的结果

if args.is_voting == "hard":
    hard_voting(voting_model_name,choose_file_list=choose_kflod_part)
if args.is_voting == "soft":
    soft_voting(voting_model_name,choose_file_list=choose_kflod_part)

3. code/preprocess/processor.py

附言:在第二章提到的下载 Bert 预训练模型时,vocab.txt 中每个字对应了一个索引,在调用 tokenizer 时,就会为文本序列中的每个字对应一个索引值

def __getitem__(self, i):
    # 记载数据集中的一条数据
    text = self.data.iloc[i]['text']

    # 利用分词器 tokenizer 将文本按字分词,并在 vocab.txt 文本中找到字的对应索引,返回索引序列
    tokenized_input = self.tokenizer(text, truncation=True, max_length=self.max_seq_len)

    # 通过设置的最大序列长度 - 当前的序列长度来判断序列填充的长度值
    pad_length = self.max_seq_len - len(tokenized_input['input_ids']) + 1

    # 用一个 0 序列添加到后面,保证序列的长度一致,使得后续矩阵运行一致
    tokenized_input['input_ids'] = tokenized_input['input_ids'] + [self.tokenizer.pad_token_id] * pad_length

    # 设置掩码,掩掉 pad 填充的部分
    tokenized_input['attention_mask'] = tokenized_input['attention_mask'] + [0] * pad_length
    if self.prediction:
        return {
            'input_ids': torch.tensor(tokenized_input['input_ids'], dtype=torch.long),
            'attention_mask': torch.tensor(tokenized_input['attention_mask'], dtype=torch.long),
        }
    intents = self.data.iloc[i]['label']
    tokenized_input['intent_labels'] = self.intent2id[intents]

    # 字典的形式返回文本的索引序列,掩码序列,以及标签
    return {
        'input_ids': torch.tensor(tokenized_input['input_ids'], dtype=torch.long),
        'attention_mask': torch.tensor(tokenized_input['attention_mask'], dtype=torch.long),
        'intent_labels': torch.tensor(tokenized_input['intent_labels'], dtype=torch.long),
    }

文本: ‘感觉好像是文科生看一本《高等数学》的教材一样,流水账一般,只是背景很好罢了,选择在这样一个竞争激烈的时代,写了那么一个催人奋进的故事,文笔不咋地。’

文本结果如下:
在这里插入图片描述

4. code/utils/model_utils.py

XFBert 中 Bert + BiLSTM + BiGRU

self.model = BertModel.from_pretrained(MODEL_NAME)
        if n_ambda > 0.:
            print(n_ambda)
            for name, para in self.model.named_parameters():
                self.model.state_dict()[name][:] += (torch.rand(para.size()) - 0.5) * n_ambda * torch.std(para)
        self.config = BertConfig.from_pretrained(MODEL_NAME)
        self.intent_num_labels = intent_dim

        self.lstm = nn.LSTM(input_size=self.config.hidden_size, hidden_size=512, batch_first=True, bidirectional=True)
        self.gru = nn.GRU(input_size=1024, hidden_size=512, batch_first=True, bidirectional=True)

5. code/utils/trainer.py/get_optimizer_and_schedule

AdamW 优化器

optimizer = AdamW(optimizer_grouped_parameters, lr=args.learning_rate, weight_decay=args.weight_decay,eps=1e-6,correct_bias=True)

学习策略: 梯度消失策略,设置了步长 num_warmup_steps

scheduler = get_scheduler(args.scheduler_name,
                          optimizer,
                          num_warmup_steps= num_warmup_steps,
                          num_training_steps=num_training_steps)

学习策略: 余弦退火(二选一)

scheduler = CosineAnnealingLR(optimizer, T_max=100)

6. code/utils/optimization_tools.py(get_scheduler 具体实现不懂)

7. code/utils/trainer.py/do_train

损失函数为: 交叉熵损失函数

intent_loss_fct = CrossEntropyLoss()

设置早停策略

if stop_nums >= args.early_stopping:
    break

训练模型,获取损失

input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
intent_labels = batch['intent_labels'].to(device)
intent_logits = model(input_ids, attention_mask=attention_mask)
intent_loss = intent_loss_fct(intent_logits.view(-1, len(intent2id)), intent_labels)
loss = intent_loss

反向传播更新参数,这里的 (step + 1) % iters_to_accumulate,意为在进行批处理时,可能由于内存原因不能设置太大的批次,通过多个较小的批次共同计算损失之后再更新参数,用于近似大批次训练效果

scaler.scale(loss).backward()
if (step + 1) % iters_to_accumulate == 0:
    scaler.step(optimizer)
    scaler.update()

更新后参数设置为 0,防止影响下一次迭代更新

model.zero_grad() # 前一步的参数清除

计算模型在验证集上的准确率

eval_intent_score = evaluation(args,model,dev_dataloader,device,ema=ema,labels_name=list(intent2id.keys()))
# print("eval acc: %.5f" % eval_intent_score )

模型保存

model_to_save = model.module if hasattr(model, 'module') else model
torch.save(model_to_save.state_dict(), os.path.join(args.save_dir_curr, "pytorch_model.bin"))
model_to_save.config.to_json_file(os.path.join(args.save_dir_curr, "config.json"))

8. code/utils/trainer.py/do_sample_predict

带入模型计算标签,不做特别说明

9. code/predict.py 单个样本的预测

记载分词器

tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR[args.MODEL_NAME])

加载模型

if "robert" in args.MODEL_NAME:
    model = XFRoberta(MODEL_DIR[args.MODEL_NAME], intent_dim=len(intent2id))
else:
    model = XFBert(MODEL_DIR[args.MODEL_NAME], intent_dim=len(intent2id), enable_mdrop=args.enable_mdrop)

加载模型参数

args.save_dir_curr = os.path.join("../user_data/checkpoint", 'parameter_{}.pkl'.format(args.MODEL_NAME))
model.load_state_dict(torch.load(args.save_dir_curr, map_location=torch.device('cpu')))

执行预测

result = predict(args, model, input_text, device, labels_name=id2intent)

References

如何下载和在本地使用Bert预训练模型

  • 1
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值