【Pytorch】BERT+LSTM+多头自注意力(文本分类)

2023-10-27

【Pytorch】BERT+LSTM+多头自注意力(文本分类)

2018年Google提出了BERT[1](Bidirectional Encoder Representations from Transformers)预训练模型,刷新了11项NLP任务的精度,在NLP领域掀起一波预训练(pre-training)模型热潮。通过对BERT、RoBERTa、GPT等预训练模型微调(fine-tuning)或者作为文本的特征提取器进行迁移学习成为当时直到现在最流行的文本分类方法。

1. BERT基本原理

主流的预训练模型包括:BERT、XLNET、Roberta、MacNet、GPT等,这些预训练模型大多是BERT based。BERT由Transformer的encoder堆叠而成,可以简单的分为3层:输入层、中间层、输出层;输出层有两个输出,一个是句嵌入(pooler output),即文本的开始标志[CLS]的最后一层输出,包含了整个文本的特征;另一个是字嵌入(sequence output),即文本所有字token的最后一层输出,其基本结构如下图 :
图 1 BERT基本结构
图 1 BERT基本结构

可以看到BERT基本结构由12个Transformer的encoder组成,提供了pooler output和sequence output两个输出。
为了使得BERT模型能够适应各类任务,统一将输入格式转化为:

其中[CLS]表示分类任务的特殊token,输出为pooler output,[SEP]为分隔符。
此外,BERT和Transformer一样也加入了位置编码 position encoding,使用的方法是类似词嵌入的方式(Parametric),直接获得位置嵌入。
由于对输入进行了改造,使得模型可能有多个句子segment,为了识别字token属于哪个句子,需要加入segment的嵌入编码,所以BERT的输入融合了word embedding、position encoding和segment embedding,如下图:
在这里插入图片描述

2. BERT的一般过程

BERT等预训练模型分为两个阶段:

  1. 预训练阶段,通过在海量语料库上以无监督学习的方式为文本学习语言特征,即从一个文本到一组特征的过程,这组特征可以是一个新的文本也可以是一组标签的概率等。
  2. 微调阶段,待模型预训练好后,可以直接使用BERT将文本转换为可以动态学习的特征,即把BERT视为神经网络的特征提取器,并在前向传播时对文本进行(字、句)嵌入,反向传播时动态学习并修改这个嵌入,然后在这个特征提取器的基础上再添加一个网络层便可以完成对特定任务的微调;比如情感分析任务,只需在pooler output层的后面加一个全连接层,神经元个数为情感类别数,再经过softmax即可得到情感分类概率,或者将Sequence output视为字嵌入结合LSTM、CNN等模型。所以说BERT是一个可以在不同NLP领域进行迁移学习的模型。

3. RoBERTa的使用

RoBERTa由Yinhan Liu等人[4]在2019年提出,他们在BERT的基础上进一步精化和优化,主要在三方面对BERT做出改进:

  1. 参数量:更大的batch_size,更多的训练样本,还使用BPE(Byte-Pair Encoding)来处理文本数据。
  2. 优化器:原BERT优化函数采用的是Adam默认的参数,其中β_1=0.9,β_2=0.999,在RoBERTa中考虑采用了更大的batches,所以将β_2改为了0.98。
  3. 训练策略:改用了动态掩码的方式训练模型,证明了NSP(Next Sentence Prediction)训练策略的不足;

RoBerta有两个输入:
1.输入索引(input_ids),输入文本各字在vacab中的索引,需要设置一个文本最大长度sequence_length,长截断,短用0填充。size: [batch_size, sequence_length].
2.注意力遮掩(attention_mask),由于文本是变长的,且有填充操作,为了识别文本真实长度/需要mask的长度,设置attention_mask,字用1表示,填充用0表示。size: [batch_size, sequence_length].
RoBerta有两个输出:
1.CLS]的输出(pooler output),对应文本标识符[CLS]的最后一层输出,包含文本整体特征,可作为文本的句嵌入。size: [batch_size, WordVec_size]。
2.序列输出(sequence output),对应的是序列中的所有字的最后一层输出,可视为文本的字嵌入。size: [batch_size, sequence_length, WordVec _size]。

4. RoBERTa- LSTM -多头自注意力模型

LSTM结合多头自注意力模型可见本人的上上篇博客,本文将结合RoBERTa - LSTM - 多头自注意力(Muti-Attention)三者建立分类模型(在跑实验的时候可以设置为BiLSTM或者BiGRU)。
上文说到,RoBerta有两个输出,一个是[CLS]的输出,可作为文本的句嵌入,另一个是序列输出(sequence output),可视为文本的字嵌入,那么我们能不能同时结合两个输出做文章呢?简单地说就是,将字嵌入通过LSTM -多头自注意力得到一个新的句嵌入,然后将该句嵌入和RoBERTa的句嵌入concat,这样不就得到了一个同时结合了RoBERTa - LSTM - 多头自注意力的句嵌入了吗,再将其输入到全连接层(分类器)即可进行文本分类任务了!(其他分类任务同理,不同点只在于数据预处理)
具体流程见下图:
在这里插入图片描述

5. 模型部分源码

模型基于pytorch平台,只展示部分关键代码。
网络参数设置:

class Parameters:
    def __init__(self, parameters):
        # 设置超参数
        self.Doc_Size = parameters[0]      # 文本词数
        self.WordVec_Size = parameters[1]  # 词向量长度
        self.WeightDecay = parameters[2]   # 权重衰减系数
        self.epoches = parameters[3]       # 迭代次数
        self.batch_size = parameters[4]    # 小批量样本数,即每次训练batch_size个
        self.features = parameters[5]      # 分类特征数,共有6个情绪{'angry', 'fear', 'happy', 'neutral', 'sad', 'surprise'}
        self.learning_rate = parameters[6]  # 学习速率/梯度下降的步长
        self.Epsilon = parameters[7]       # -
        self.dropout_rate = parameters[8]  # dropout的概率一般0.0~0.5
        self.seed = parameters[9]          # 随机种子
        self.hidden_dim = parameters[10]   # 隐藏状态的维数/隐藏层节点个数,视词向量维度而定
        self.n_layers = parameters[11]     # lstm隐藏层层数
        self.bidirect = parameters[12]     # 是否双向
        self.cuda = parameters[13]         # 是否使用GPU
        self.num_heads = parameters[14]    # 注意力机制的头数
        self.dim_k = parameters[15]        # query and key的维数,必须是num_heads的整数倍,大小取决于词向量维度,大小适中即可
        self.dim_v = parameters[16]        # value的维数
        self.bert_path = './hfl-RoBERTa-wwm-ext, Chinese/'

# 通过一个向量直接设置所有参数,方便调参
ARGS = Parameters([150, 768, 1e-5, 6, 48, 6, 1e-5, 1e-8, 0.1, 12, 768, 1, True, True, 4, 768, 768])

模型定义,优化器设置,不同层学习率等:

	# 2. 模型定义
    # 先定义robert,BertModel用来获取词向量
    robert = BertModel.from_pretrained(ARGS.bert_path).to(device)
    # 再定义attention
    # 以robert作为Roberta_LSTM的参数,因为robert也要在__init__中参与反向传播
    robert_lstm_att = Roberta_LSTM_Att(robert).to(device)

    lstm_params = list(map(id, robert_lstm_att.lstm.parameters()))
    dense_params = list(map(id, robert_lstm_att.dense.parameters()))
    robert_params = list(map(id, robert_lstm_att.robert.parameters()))
    # 除lstm_params + dense_params + robert_params之外的基础参数,即自注意力的权重系数
    base_params = filter(lambda p: id(p) not in lstm_params + dense_params + robert_params,
           robert_lstm_att.parameters())
    # 3. 定义和梯度更新算法AdamW,优化器optimizer
    t_total = len(train_loader) * ARGS.epoches
     # 下列参数 不进行正则化(权重衰减)
    no_decay = ["bias", "LayerNorm.bias", "LayerNorm.weight"]
    # 设置不同分层的学习率
    optimizer_grouped_parameters = [
        {
            "params": base_params,
            "weight_decay": ARGS.WeightDecay,
            "lr": ARGS.learning_rate*10  # 自注意力层的学习率
        },
        {
            "params": [p for n, p in robert_lstm_att.robert.named_parameters() if not any(nd in n for nd in no_decay)],
            "weight_decay": ARGS.WeightDecay,
            "lr": ARGS.learning_rate  # RoBERTa层的学习率
        },
        {
            "params": [p for n, p in robert_lstm_att.robert.named_parameters() if any(nd in n for nd in no_decay)],
            "weight_decay": 0.0,
            "lr": ARGS.learning_rate  # RoBERTa层的学习率
        },
        {
            "params": [p for n, p in robert_lstm_att.lstm.named_parameters() if not any(nd in n for nd in no_decay)],
            "weight_decay": ARGS.WeightDecay,
            "lr": ARGS.learning_rate*10  # LSTM层的学习率
        },
        {
            "params": [p for n, p in robert_lstm_att.lstm.named_parameters() if any(nd in n for nd in no_decay)],
            "weight_decay": 0.0,
            "lr": ARGS.learning_rate*10  # LSTM层的学习率
        },
        {
            "params": [p for n, p in robert_lstm_att.dense.named_parameters() if not any(nd in n for nd in no_decay)],
            "weight_decay": ARGS.WeightDecay,
            "lr": ARGS.learning_rate*10  # 全连接层的学习率
        },
        {
            "params": [p for n, p in robert_lstm_att.dense.named_parameters() if any(nd in n for nd in no_decay)],
            "weight_decay": 0.0,
            "lr": ARGS.learning_rate*10
        },
    ]

    optimizer = AdamW(optimizer_grouped_parameters, lr=ARGS.learning_rate, correct_bias=False)
    # loss_func
    criterion = nn.CrossEntropyLoss()
    # scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lambda epoch: 0.99**epoch, last_epoch=-1)
    scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=t_total)
    # print(scheduler.get_lr())
    # for param_group in optimizer.param_groups:
    #     print(param_group['weight_decay'])

forward源码:

def forward(self, batch):
        # input_ids, attention_mask, token_type_ids
        # output[0]: [batch, Doc_Size, WordVec_size]  文本矩阵 接CNN,LSTM等
        # output[1]: [batch, WordVec_size]  文本向量 接MLP,SVM等
        input_ids, attention_mask, token_type_ids, labels = batch
        outputs = self.robert(input_ids=input_ids,
                              attention_mask=attention_mask,)
        # output[0]: [batch, 128, 768]
        out1, h_n = self.lstm(outputs[0])  # lstm层
        out1, weights = self.MultiAttention1(out1, h_n)
        input = torch.cat([out1, outputs[1]], dim=-1)
        outputs = self.dense(input)  # 全连接层
        return outputs

实际上,Roberta层和LSTM、MA、Dense层需要设置不同的学习率,Roberta层稍小,一般为1e-5左右,LSTM、MA、Dense层稍大一般为1e-4左右。此外,代码细节过多,不作过多展示,需要的可私信

6. 消融实验

消融实验(Ablation experiment)[5] 是为了证明组合模型的整体性,即证明模型的各个部分是否都在发挥作用。方法就是逐一把模型的各个组成部分从模型中去除,然后判断对模型的影响程度,如果有下降,则说明该组成部分是有用的不可分割的,反之就是可有可无的,如果都有下降则证明模型是统一的,每个部分都是不可或缺的,对于RBs-BG-MA模型,我们进行如下消融实验以证明模型的整体性(数据集是文本六分类):
在这里插入图片描述

我们将模型分解为:句嵌入、字嵌入+LSTM、MultiAttention三个模块,为此逐个移除其中一个模块来判断对模型的影响程度,可以看到:
移除句嵌入模块后(移除后为RB-BG -MA模型)模型准确率下降了1.24%左右, F_Macro下降了1.40%左右,移除多头自注意力模块后(移除后为RBs-BG模型)准确率下降了1.66%左右, F_Macro下降了2.11%左右,移除字嵌入模块后(移除后为RB-MLP模型)准确率下降了1.73%左右, F_Macro下降了1.63%左右,可见这三个模块对于模型都有重要作用;结果也显示,纯RoBERTa的性能已经很好了,单独使用某一两个模块反而会降低RoBERTa的性能,可见RBs-BG-MA模型的性能是各个模块共同作用的结果,缺一不可。

需要源码的同学可私信我哦^ ^


[1] Devlin J, Chang M-W, Lee K, et al. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. arXiv preprint, arXiv:1810.04805 [cs.CL] 2018.

[2] jacobdevlin-google. bert[EB/OL].https://github.com/google-research/bert. 11 Mar 2020.
[3] Cui Y, Che W, et al. Pre-Training with Whole Word Masking for Chinese BERT[J]. 2019.
[4] Liu Y, Ott M, Goyal N, et al. RoBERTa: A Robustly Optimized BERT Pretraining Approach. arXiv preprint, arXiv:1907.11692 [cs.CL] 2019.
[5] Ren S, He K, Girshick R, et al. Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence, 2017, 39(6):1137-1149.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Pytorch】BERT+LSTM+多头自注意力(文本分类) 的相关文章

随机推荐

  • 文本生成视频Make-A-Video,根据一句话就能一键生成视频 Meta新AI模型

    Meta公司 原Facebook 在今年9月29日首次推出一款人工智能系统模型 Make A Video 可以从给定的文字提示生成短视频 Make A Video研究基于文本到图像生成技术的最新进展 该技术旨在实现文本到视频的生成 可以仅用
  • 有序序列的二分查找

    二分算法思想 1 定义下标min指向第一个元素 定义max指向最后一个元素 2 定义下标mid等于 min max 2 3 判断arr mid 是否等于要查找的数 若等于返回mid值 若不等于 如果小于 则min mid 1 如果大于 则m
  • SSH远程访问控制

    目录 一 SSH概述 1 1什么是SSH 1 2SSH的作用 1 3SSH的主程序以及配置文件 二 SSH远程登录方式 2 1ssh 远程主机用户名 远程服务器主机名或IP地址 p port 2 2ssh l 远程主机用户名 远程服务器主机
  • Uboot初次编译、烧写、启动(启动界面log简析)

    目录 U Boot 初次编译 U Boot 烧写与启动 上述笔记第三点就是建立shell脚本实现的 第四点就是修改Makefile文件实现的 下面均有实现步骤讲解 U Boot 初次编译 先编译体验一下正点原子提供的UBOOT 首先在Ubu
  • 刷题之01 矩阵

    给定一个由 0 和 1 组成的矩阵 mat 请输出一个大小相同的矩阵 其中每一个格子是 mat 中对应位置元素到最近的 0 的距离 两个相邻元素间的距离为 1 示例 1 输入 mat 0 0 0 0 1 0 0 0 0 输出 0 0 0 0
  • 阅读文献1:Bootstrapping ViTs: Towards Liberating Vision Transformers from Pre-training(文章翻译及自身的理解和总结)

    目录 前言 一 文章标题 二 摘要 我在这里直接翻译过来 三 Introduction 这里也没有什么好解释哒 我也直接翻译过来 四 Related Work 1 Vision Transformers 2 Knowledge Distil
  • http之GET请求的传递参数的方式

    第一种 直接在URL后面加参数 localhost 21811 Handler1 ashx id 1 name abc 如下图 第二种 用超链接的方法传递参数 当点击超链接的时候 首先会跳转 localhost 21811 Handler1
  • B/S架构及其运行原理 #CSDN博文精选# #IT技术# #软件模式# #架构模式#

    大家好 小C将继续与你们见面 带来精选的CSDN博文 又到周一啦 上周的系统化学习专栏已经结束 我们总共一起学习了20篇文章 这周将开启全新专栏 放假不停学 全栈工程师养成记 在这里 你将收获 将系统化学习理论运用于实践 系统学习IT技术
  • vue导出自定义的excel表格

    1 Vue导出excel 1 1 安装依赖 npm install S file saver npm install S xlsx npm install D script loader 1 2 配置 在项目src文件夹下新建文件夹vend
  • git bash 操作 从github上pull代码再从客户端push上去

    如何将github上的代码拖到本地 1 cd切换目录到对应的仓库 2 git init来初始化这个仓库 3 用ssh keygen t rsa c 邮箱 来设置ssh 然后拷贝到对应的github上设置密钥 4 ls 可以把目录下的文件列出
  • 图文详解丨iOS App上架全流程及审核避坑指南

    目录 目录 引言 一 登录开发者账号注册账号 二 申请证书 描述文件 证书管理 描述文件管理 三 代码实现 四 总结 引言 到了2021年 虽然网上也有大牛写过很多IOS App上架流程资料 但随着苹果发布机制的微调有些 已经过时了 我就趁
  • 【半监督学习】1、Mean Teacher

    文章目录 一 背景 二 方法 三 效果 论文 Mean teachers are better role models Weight averaged consistency targets improvesemi supervised d
  • [ROS学习之路]Ubuntu16.04下使用QT4 + ROS

    1 环境声明 Ubuntu 16 04 ROS kenitic Levi Armstrong大佬已经开发了对应的qt插件 ros qtc plugin使我们能在qt creator上编译ROS程序 这个插件以前使用ppa的方式进行安装 现在
  • java处理图片失真的问题-水印-裁剪-压缩

    问题描述 某些图片 的背景色是透明 但是java 处理后 可能变黑色 其实问题原因就是失真啦 解决办法 BufferedImage image new BufferedImage width height BufferedImage TYP
  • 浅谈VMware Workstation Pro 使用Linux系统(超详细教程)

    Vmware Workstation Pro 使用Linux系统 分割线 目录 VMware Workstation Pro安装 Linux系统镜像下载安装并在VMware Workstation Pro中使用 安装CentOS 查看ip地
  • 可重入锁和不可重入锁的的区别及原理

    锁 把所需要的代码块 资源 或数据锁上 在操作他们的时候只允许一个线程去做操作 不可重入锁 当A方法获取lock锁去锁住一段需要做原子性操作的B方法时 如果这段B方法又需要锁去做原子性操作 那么A方法就必定要与B方法出现死锁 这种会出现问题
  • C语言--用队列实现栈

    用队列实现栈 把数据从队头出来 再插入到队尾 最后一个不插入 直接出栈就实现了出栈操作 取栈顶元素就是获取队尾元素 其余操作入栈 判空 销毁类似于队列操作 可以用一个队列实现栈 也可以用俩队列 我用的一个队列 typedef int typ
  • C++引用

    引用是一个 共享其它变量内存 的变量 我们称这种操作是 引用一个变量 或 给变量取别名 之前说过 变量名称关联变量内存 在变量名前加 符 就可以得到内存地址 而变量名 则 体现 了内存中的数据 而给变量取别名 实际上是把图中的 别名 和内存
  • VSCODE 修改默认编码格式

    VSCODE 修改默认编码格式 快捷键 CTRL SHIFT P 输入Settings 打开设置 找到文件编码格式 ends
  • 【Pytorch】BERT+LSTM+多头自注意力(文本分类)

    Pytorch BERT LSTM 多头自注意力 文本分类 2018年Google提出了BERT 1 Bidirectional Encoder Representations from Transformers 预训练模型 刷新了11项N