费曼说:学习一件事情最好的方式是做它的老师,这也是写这篇博文的目的,写这篇博文,即便有其他原因,但更多的还是写给自己,话不多说,让我们开始进入NLP吧
本次的内容主要还是对赛题进行对于BERT的实践过程,BERT相较于先前学习的几个模型,它的架构复杂度有了质的变化,同时,也由于版本兼容性,这也给实践带来了许多问题
任务说明:任务task6
基座课程:基座课程
1.方法解释
1.Transformer原理
对于Transformer,最先发表的论文是Attention is All you need中提出的,模型的编码是一组编码器的堆叠,模型的解码部分是由相同数量的解码器的堆叠。
让我们退回到2017 年,Google 提出了 Transformer 模型,用全Self Attention
的结构,取代了以往 NLP 任务中的 RNN 网络结构,在 WMT 2014 Englishto-German
和 WMT 2014 English-to-French
两个机器翻译任务上都取得了当时 SOTA 的效果。
但是,这个模型更加重要的贡献是:使得模型训练过程能够并行计算。在 RNN 中,每一个 time step 的计算都依赖于上一个 time step 的输出,这就使得所有的 time step 必须串行化,无法并行计算,如下图所示。这就是Transformer相对于RNN具有的重要的优势,并行运算
Transformer 使用了 Seq2Seq
任务中常用的结构——包括两个部分:Encoder 和 Decoder。一般的结构图,都是像下面这样。两个框内,分别是编码器和解码器。
让我们先从编码器开始
1.Encoder:
Transformer 的论文中使用了 6 层编码器,这里的层数 6 并不是固定的,你也可以根据实验效果来修改层数)。同理,解码部分也是由多层的解码器(decoder)组成(论文里也使用了 6 层的解码器)。
每一个编码器 在结构上都是一样的,但它们的权重参数是不同的。每一个编码器里面,可以分为 2 层
- Self-Attention Layer
- Feed Forward Neural Network(前馈神经网络,缩写为 FFNN)
让我们把目光放的再小一些,来到FC层与self-attention层的结构:
上面的注意力机制的缩写与下面向量的缩写是一一对应的:
而以上这些Query、keys、values向量的产生,是由对应的权重矩阵与输入向量相乘所得到的:
因此,它们的工作机制如下:
同时,Encoder与Decoder也存在注意力机制:如图所示,即我们需要多个权重矩阵来对于输入词向量进行处理:
Decoder和Encoder的注意力机制:
2.Decoder:
Decoder的输入分为两类:
一种是训练时的输入,一种是预测时的输入。训练时的输入就是已经对准备好对应的target数据。例如翻译任务,Encoder输入"Tom chase Jerry",Decoder输入"汤姆追逐杰瑞"。
预测时的输入,一开始输入的是起始符,然后每次输入是上一时刻Transformer的输出。例如,输入"",输出"汤姆",输入"汤姆",输出"汤姆追逐",输入"汤姆追逐",输出"汤姆追逐杰瑞",输入"汤姆追逐杰瑞",输出"汤姆追逐杰瑞"结束。
3.除此之外:
1.为了使模型保持单词的语序,模型中添加了位置编码向量。如下图所示,每行对应一个向量的位置编码。因此,第一行将是我们要添加到输入序列中第一个单词的嵌入的向量。每行包含512个值—每个值都在1到-1之间。因为左侧是用sine函数生成,右侧是用cosine生成,所以可以观察到中间显著的分隔。
位置向量原理图:
编码器结构中值得提出注意的一个细节是,在每个子层中(Self-attention, FFNN),都有残差连接,并且紧跟着layer-normalization。如果我们可视化向量和LayerNorm操作,将如下所示:
2.残差网络就是一种防止模型层数过大,梯度下降机制失效的结构:一句话总结就是,在输出前面加上输入,但是不限制跨越的层数:
4.输出:
Output如图中所示,首先经过一次线性变换,然后Softmax得到输出的概率分布,然后通过词典,输出概率最大的对应的单词作为我们的预测输出。
2.代码实践:
1.种子的设置:方便代码复现,即在不同的硬件环境下复现相同的结果
2.训练数据集的十折划分,即把数据集随机划分为十份,其中通常有一份作为验证集用以获取超参数的取值,(按十四种分类进行均分):
其中 all_data2fold
的作用把每个类别的数据平均分为 10 份,返回的数据是 fold_data
,是一个 list,有 10 个元素,每个元素是 dict,包括 label 和 text 的 list。
然后分别写入到 10 个文件中:train_0
、train_1
…train_9
。每篇文章之间添加一个空行作为文章之间的分隔符
3.划分验证集,导入测试集:可以看到,测试集的id为9
4.导包
5.创建数据字典:我们创建字典,保存训练集中所有的单词,并添加 [PAD]
,[UNK]
, [CLS]
,[SEP]
, [MASK]
等 token。
6.attention代码:存在线性模型,同时对于全零向量置零
7.编码器:Encoder
8. WhitespaceTokenizer:对于非全零向量,导入wordvocab.txt文件进行编码
9.SentEncoder(句子编码器):LSTM机制的应用
10.建立模型:观察到模型有7.72M的parameter
11.优化器
参数如下:learning_rate = 2e-4(学习率)
bert_lr = 5e-5(bert学习率)
decay = .75(decay_rate:衰减系数 (学习率的衰减系数))
decay_step = 1000(衰减速度 (相当于iteration ,总样本/batch-size))
12.建立数据集
13.数据集加载器
14.得分统计函数
15.训练
16.训练
17.遇到的问题
由于版本不兼容所导致的问题
一些问题:
由于BERT的代码较长,参考的课程案例的代码为三年前的代码,许多环境配置都与现在的线上平台及线下的python环境存在着不兼容的问题,我进行了许多次尝试:
kaggle:出现未知报错(未来得及截图)
colab:中存在着对于TensorFlow1及之前的版本不兼容的问题,据查阅资料,在2020年前,我们只需要一段代码就可以完成TensorFlow版本切换,而在今天,我们需要使用更加复杂的方式:
但colab与国内存在着网络连接的问题,这种方式没有经过实践。
本地:存在着python3.9与对应的TensorFlow、keras等包存在版本冲突的问题,这也同时将我引入到了python包管理这样的一个问题中来,不过由于前面的探索消耗了很多的时间,到ddl前,我还没有对于环境管理有一个明确的实践经验,这也是我需要再深入的地方,同时,在查阅许多代码的同时,在readme中(通常需要标明执行环境),大多数作者,甚至是非常熟练的代码人都没有给出环境甚至没有这种意识,这也是需要补齐的短板。代码的效率固然是重要的,但作为一段好的代码,它的适用性也是需要关注到的,这也是我需要注意的。
3.总结
本次对于BERT和模型的架构通体都有了一定的认识,对于自注意力机制有了更加明确的理解,总体来说还是很有收获的,但在时间的投入上海有待进一步地实践