粉丝8.6万获赞12.7万


bird 和 gpt 在 结构上的差异决定了它们分别更适合哪些任务,你可以结合实际场景说一下。嗯,这个我当时是这么理解的。我觉得 bird 和 gpt 的 差异其实不太适合从模型强不强来比, 而是它们一开始就是奔着不同问题去设计的。先说 bird, bird 是 encoder only, 然后是双向的,这就意味着它更像是在先把一句话读明白, 所以放到真实业务里,比如搜索审核,意图识别这种场景,我们更关心的是用户这句话到底在说什么,而不是下一句话怎么写。我之前做搜索意图的时候就有这个体感, 用 burt 去判断低卡套餐到底是减脂需求,还是只是描述稳定性会明显好一些, gpt 就 完全是另一种思路了。 gpt 是 decod only, 而且是一步一步往下生成的,它天生就是用来接话的,比如对话或者客服场景, 用户一句话丢过来,你需要立刻生成一个合理回复,这时候 gpt 就 会比 bert 顺很多。让 bert 去补一句话,其实会发现很别扭,他不是干这个的。所以我会觉得一个比较直观的理解是, bert 更像是在做阅读理解, gpt 更像是在写作文。一个解决的是你在说什么,一个解决的是我接下来该怎么说。当然它们也各自有代价。 bert 不 太适合长文本生成,成本高,还不好控。 gpt 在 理解类任务上,如果上下文或者数据不够,其实是会票的。所以在系统设计里,我一般不会纠结选谁,而是会拆任务,该理解的地方交给 bird, 该输出的地方交给 gpt。 结构选对了,后面很多问题都会自然变简单。嗯,大概就是这样。

今天我们要讲的呢是如何用贝尔特模型对斯坦福问答数据集进行微调来实现一个问答系统。 嗯,这个问答系统呢是可以在给定的段落当中找出能够回答问题的文本片段,对这个还是很有意思的,那我们就直接开始吧。我们先来说说就这个 s 跨的数据集到底是个什么东西?它在这个问答任务里面扮演一个什么样的角色? s 跨其实就是斯坦福问答数据集的英文缩写, 然后它里面每一个数据点呢都包括一个问题和一个用来回答这个问题的上下文段落,那任务就是要在这个段落里面准确地找出能够回答问题的那个文本片段。那大家一般用什么指标来衡量这个模型在 squatch 上面的表现呢?最常用的一个指标就叫做精确匹配, 嗯,就是看模型预测出来的答案和真实的答案一模一样的占比是多少哦,就直接是算这个比例了解了,那我们再来说一下这个 bert tokenizer 的 设置, 我们要怎么在代码里面去加载和保存这个预训练的 bert tokenizer 其实很简单,我们就直接用这个 from pretrint 这个函数就可以把它 in face 上面的 bert base on cast 的 这个 tokenizer 给加载进来。 然后呢我们可以把它保存到本地的一个目录里面,如果说这个目录不存在的话,我们要先创建一下,那如果说我们要去使用这个更快的 word piece tokenizer, 我 们要怎么去操作呢?很简单,就是我们直接用这个 word piece tokenizer 这个类,然后把我们之前保存的那个 vocab 点 txt 给它加载进来就可以了。 然后呢把这个 lowercase 设为 true, 就是 让它都转成小写好的,那这个 s 跨的数据集它的原始格式是什么样子的?我们要怎么把它读进来?它其实就是一个 json 格式的文件,然后我们可以直接用这个 keras dot u t i l s dot get file 这个函数,就可以把这个训练级和验证级都下载到本地。原来是这样,那我们现在已经把数据读进来了,那我们要怎么把它处理成我们模型可以用的这种格式呢?嗯, 首先呢,我们会去便利这个 json 文件,然后把每一条数据都转成一个 squad example 的 对象,这里面就会包括比如说问题啊,上下文啊,答案的起始位置啊这些信息。听起来这个 squad example 类还挺关键的,那它还做了哪些事情? 这个类里面它有一个 pre process 方法,就是负责对上下文问题和答案做一些清理,然后呢会找到答案在这个文本里面的其实和答案做一些清理,然后呢会找到答案在这个上下文和问题进行编码, 接着会找到答案所对应的 token 的 起始和结束的位置。最后呢会把所有的这些 input id, s 啊, token, type ads, attention, mask 啊都给生成出来,同时也会做一些 padding, 那这些东西其实都是我们的模型训练所需要的。明白了,那这个 squad example 对 象到底是怎么被构建出来的?然后它里面的这些预处理的步骤到底是在干什么?首先呢,这个 squad example, 它的出场函数里面会传入这个问题啊,上下文啊,答案的起始位置啊,还有答案的文本,以及所有的可能的答案, 然后他会先把这些东西都存下来,接着他会有一个 pre process 方法,这个方法里面会先对这个上下文问题和答案都做一些清理,就是把多余的空格啊这种东西都去掉。那答案的位置信息是怎么被处理的呢? 就是他会先根据这个起始位置和答案的长度算出答案在这个上下文里面结束的位置,然后他会去标记这个上下文里面哪些字体是属于答案的,接着再去 tokenize 这个上下文, 之后呢,会去找到哪些 token 是 完全被答案所覆盖的,然后把这些 token 的 index 找出来,如果说没有找到任何一个 token 是 属于答案的,那这个样本就会被跳过。 最后呢,他会再去 tokenize 这个问题,然后把这个上下文和问题的 token ids 拼起来,再生成 token type ids 和 attention mask。 同时呢也会做一些 padding, 或者是说直接跳过这个样本,如果它太长了的话, ok, 那 我们怎么把这个 square example 的 对象转成模型训练用的这个输入和标签呢?呃,首先我们会定义一个函数叫 create inputs targets, 它里面会先抽象几个 list, 用来存 input ids, token type ids, attention mask, 还有 start token id x 的 对象。如果说这个对象它的 skip 标记是 false 的 话, 那我们就把它的每一个字段都添加到对应的这个 list 里面,所以说最后我们得到的是一个什么样的数据结构呢?最后呢,我们会把这些 list 都转成 number array, 然后我们的输入 x 就是 一个 list, 它包含了 input ids, token type ids 和 attention mask 这三个 array。 我们的标签 y 也是一个 list, 它包含了 start token by d x 和 end token by d x 这两个 ray。 我 还有一个问题啊,就是这个原始的 s 块的数据,它是一个什么样的结构?我们要怎么把它转成这个 squad example 的 对象呢? 呃,这个 s 块的接收文件,它的最顶层是一个 data 字段,然后它下面是很多个段落,每个段落里面也有 context 的 字段,就是上下文, 还有一个 qas 字段,它是一个 list, 里面存了很多个问题和答案的对象。那我们怎么去便利这个结构,把它变成我们想要的样子呢?就是我们会去便利这个 data, 然后再进到每一个 paragraph 里面, 把这个 context 拿出来之后呢,再去便利这个 qas。 然后对于每一个问题答案,对,我们都会把这个问题上下文,答案的文本,答案的起始位置, 以及所有可能的答案都拿出来,创建一个新的 squad example 对 象,然后把它加到一个 list 里面,最后我们会返回这个 list。 好 的,那我们这个模型的架构到底是怎么搭起来的?用的是 keras 的 函数式 api。 对, 那具体是怎么实现的?我们首先会加载这个 bert 的 域训练模型作为我们的 encoder。 然后呢,我们会定义三个输入层,分别是对于 input ids, token ids 和 attention mask, 它们的长度都是我们之前设定好的那个 max length。 那 这些输入是怎么接到 bot 上面,然后又是怎么输出的呢?我们会把这三个输入都接到这个 bot encoder 上面,然后我们会取它的那个 sequence output。 接下来呢,我们会分别通过两个 dance 层得到 start logits 和 endlogits。 在 经过 flatten 和 softmax 之后呢,就得到了 start props 和 end props, 这两个就分别表示每个 token 是 答案的起始和结束的概率。 最后我们会用这三个输入和两个输出构建我们的 keras model。 懂了,那我们这个模型的损失函数和优化器是怎么设置的?损失函数我们用的是稀疏分类交叉商,然后我们没有从 logits 直接算,因为我们最后一层是有 softmax 的 优化器,我们用的是 adam。 然后学习率我们设的是五亿负五。然后我们会对这两个输出,也就是起始位置和结束位置的概率 分别算 loss, 然后加起来作为最终的 loss。 好 的,那如果我们想要在 tpu 上面运行我们的模型,我们要怎么去设置这个环境和模型呢?首先我们要把这个 use tpu, 把这个变量设为 true, 然后呢,我们要通过这个 tpu cluster server 来连接到 tpu, 接着我们会用这个 tpu strategy 来创建一个分布式的策略。最后呢,我们要在这个 strategy scope 这个上下文里面去调用我们刚才写的那个 create model 函数, 这样我们的模型就会被正确地配置到 tpu 上面去训练了。明白了,那我们要怎么去实现这个 exact match 的 回调函数?就是它怎么去计算每个 epoch 之后的这个精确匹配的分数呢?是这样的,这个 exact match 它是继承了 keras, callbacks, pullback, 然后它的构造函数里面会接收这个 x e v l 和 y e v l 这两个参数,把它存下来,然后它会重写 on epoch and 的 这个方法。 这个方法里面呢,它会先调用 model predict 得到所有样本的这个起始位置和结束位置的预测概率。 ok, 那 它怎么去根据这个预测的结果和真实的答案去计算这个分数呢?它会便利每一个样本,然后对于每一个样本,它都会根据这个 token offset 信息, 把这个预测的起始和结束的 token 位置转成文本里面的字体的位置,然后把这个预测的答案文本拿出来。接下来呢,他会对这个预测的答案和真实的答案都做一些标准化的操作,就是转成小写啊,去掉标点啊,去掉冠词啊,然后去掉多余的空格啊, 最后他会去看这个预测的答案是不是在这个真实答案的 list 里面,如果是的话呢,就计数加一,最后算这个精确匹配的分数,就是用这个计数除以总量本书。好的,那我们这个模型在训练的时候,这个精确匹配的分数到底表现怎么样?我们在训练一个 epec 之后呢,就可以得到一个 百分之七十八的精确匹配分数,然后他也会打印出来这个训练的速度和每一个输出头的 loss。

今天的节目,我们想跟大家聊一聊如何用 transform 模型来做文本分类。是的,这个也是自然语言处理当中非常常见的一个任务, 那我们就直接开始吧。好的,咱们首先要讲的呢,就是这个 transform 模型的基本原理。说到这,你能先跟大家简单的说一下这个 transform 模型它到底是怎么工作的吗?当然可以,其实 transform 模型它是一个基于自注意力机制的深度学习模型, 那他的核心的思想就是通过计算序列中各个元素之间的关系来捕捉局的依赖信息。嗯,对,然后他的这个结构呢,是由编码器和解码器组成的。嗯,那你能再讲一讲,这个自注意力机制在这个模型当中到底是扮演一个什么样的角色呢?没问题, 这个自注意力机制呢,它可以让模型在处理某个元素的时候。嗯,不光只看这个元素本身,也会去关注整个输入序列当中的其他元素。对,然后给这些元素分配不同的权重,最后把这些带有权重的信息进行聚合,就可以得到当前元素的一个更丰富的表示。 明白了,那我们接下来要聊的就是如何用 keras 来实现这个 transformer block, 也就是这个 transformer 的 基本组成单元。对,那在实现这个 transformer block 的 时候,咱们都用到了哪些关键的层?然后每一层都起到了什么样的作用呢? 在实现的时候呢,主要的就是用到了多头自注意力层,就是 multi head attention, 然后前馈神经网络 就是 feedforward network。 对, 还有就是 layer localization 和 drop out 层,哎,那这些层是怎么组合起来完成信息的交互和特征的提取的呢?具体来说呢,首先输入会经过一个多头自注意力层, 对,这个就可以帮助不同的注意力头去关注不同位置的信息。没错,然后呢,会有一个 drop out 层,对这个输出进行一个随机的施活,再跟输入进行一个残差连接, 接着通过一个 layer normalize 对, 之后呢,再送入到前馈神经网络中,然后再经过一个 dropout 和残差连接,最后再通过一个 layer normalize 输出。那我们接下来要实现的就是这个 embedding 层,在这个 transformer 的 模型当中,这个 embedding 层具体是怎么实现的? 然后为什么要把 token embedding 和 position embedding 分 开来做呢?其实在这里呢,是用了两个不同的 embedding 层,一个呢是对 token 进行编码,另一个呢是专门对 token 的 位置进行编码,然后最后把这两个结果加起来作为最终的一个输出。 分开做的意义是什么呢?分开的原因主要是 token embedding 是 为了让模型能够学习到不同单词之间的语义关系。对,而 position embedding 呢,是为了让模型能够捕捉到单词在序列当中的位置信息。嗯,对,因为 transformer 模型本身是没有办法感知到顺序的,所以这个位置的信息是非常重要的。 那我还有一个问题,就是这个 position embedding 是 怎么实现的?是通过学习得到的,还是说有一个固定的公式来计算的,这里的 position embedding 也是通过一个 embedding 层来学习得到的,就是它会把这个位置的缩影映射成一个 dance vector, 然后这个 dance vector 就 会随着整个模型一起训练,最后学到一个最适合当前任务的位置。表示。明白了,那咱们现在就来进入这个实战的部分,就是我们要使用 imdb 这个影评数据集来做一个文本分类。 那在把数据喂给模型之前,我们都做了哪些数据的预处理?我们先把这个词汇表的大小限制在了两万,就是只保留这个数据集中出现频率最高的两万个词。嗯, 然后呢,把每条影屏的长度都截断到了两百个词,对,就是超过两百个词的我们就直接截掉,不足两百个词的我们就用零来补齐。所以在加载数据的时候,是直接就可以通过这个 q r s 的 a p i 就 可以把它划分成训练级和验证级嘛,直接用这个 q r s dot data size, dot m d b, dot load data 就可以了,然后它会自动地帮你分成训练级和验证级,训练级是有两万五千条,验证级也是两万五千条。好的,那我们的模型到底是怎么搭建的?就是我们在把这个 transformer 的 layer 搭好之后,它后面还接了哪些层?然后整个网络的结构是怎么设计的? 首先我们的输入是一个定长的整数训练,然后经过我们的这个 token and position embedding 层之后呢,就会得到一个,每个 token 的 一个向量表示对,接着把这个表示送入到我们的 transformer block 里面去提取特征 之后呢,我们对这个输出做一个局域的平均值化,再经过一系列的 drop out 和全连接层,最后用一个 softmax 的 全连接层做一个二分类输出,就是一个概率分布。 那这个模型在训练的过程当中,它的 loss 以及优化器是怎么设置的呢?这个模型的话我们用的是 adam 优化器,然后 loss 我 们用的是 sparse categorical cross entropy, 因为我们的标签是整数编码嘛。对,然后我们在训练的过程当中,同时也监控了这个 accuracy 这个指标。 训练了几个 epoch 之后,这个模型在训练级和验证级上面的表现怎么样?我们就训练了两个 epoch, 然后第一个 epoch 结束的时候,训练级的 accuracy 就 达到了百分之七十点七,然后验证级的 accuracy 就 已经到了百分之八十四点四四。 对,然后第二个 epec 结束的时候,训练级的 accuracy 就 提升到了百分之九十点五九,但是这个时候验证级的 accuracy 就 只提升到了百分之八十四点八八。嗯,这个时候其实已经有一点点过你和的趋势了。