优质的预训练模型。
啥意思,举个例子,你看了二十四史、资治通鉴、史记,你对历史知识以及发展规律有很多了解,但是直接让你去高考文科历史,估计难以拿高分,但是给你学习一下考纲,针对性训练下,你就可以拿很高分,估计很多应试教育的人都pk不过你。这样可以通俗理解预训练和fine-tune。
bert的预训练貌似是维基百科啥的语料,这个模型会看很多文字资料,这样会把一些语言规律、语言的语义、上下位概念之类的信息集成到这个大模型的参数之中。
那么第一步,我们先看看模型长啥样(细节上小问题先忽略哈,注重整体理解,不拘泥于一招一式的剑法,而要懂得剑意精髓)
拿好纸和笔,边看边画效果更佳哦。
1.bert模型长啥样
1.1前面说过,看模型先看输入
括号里的进阶部分,初看时可以不看。
1.1.1举个栗子:I like strawberries
tokenization:[CLS] I like straw ##berries [SEP] 其中[CLS]和[SEP]为填充字符,至于为什么可以先不管,看到最后就知道了。
向量化:
(1)将上述每一个token embedding为一个向量(进阶了解,初始化后lookup table查找也可以索引向量与embedding矩阵相乘得到),假设embedding都为128维,则得到6 * 128向量/tensor
(2)将上述每个token的position位置0 1 2 3 4 5,也各自embedding为一个向量,同上,得到6 * 128向量/tensor
(3)上述整体为一个句子,于是为0 0 0 0 0 0,如果为A、B句pairs,则前半句每个token对应这部分为0,后半句对应1;与上述token方式一样embedding,得到6 * 128向量/tensor
对于两个序列:
tokens: [CLS] is this jack ##son ##ville ? [SEP] no it is not . [SEP]
type_ids/Segment: 0 0 0 0 0 0 0 0 1 1 1 1 1 1
对于一个序列:
tokens: [CLS] the dog is hairy . [SEP]
type_ids/Segment: 0 0 0 0 0 0 0
(4)将上述暴力相加得到6 * 128向量/tensor(进阶探索,不暴力相加也行,还可以拼接为6 * 384向量/tensor然后全连接为6 * 128向量/tensor,不过既然bert他们相加就可以,估计无所谓)
1.1.2三个向量字典:
token向量字典(key为token,value为向量,随机初始化然后训练进行优化,字典长度一般几万吧);
position向量字典(key为position,value为向量,随机初始化然后训练进行优化,字典长度取决于预训练时设置的最大token序列长度,一般也就几百);
Segment 句向量字典(key为0、1,value为向量,bert是随机初始化,然后训练进行优化,字典长度为2),只有对句pairs前句都为0,后句都为1,单句情况下每个token的Segment都为0
如果没看懂,可以看下这位同学的画图,很清晰https://www.cnblogs.com/d0main/p/10447853.html。
是不是发现同样一个token,位置不一样,生成的向量可能不一样了,这才符合我们实际语言的认知啊
1.1.3源码进阶延伸
延伸1:
会设置一个input的最大字符数长度,比如200这种,多的截断,少的用某个字符补上,bert对于补上的字符做了标记,在后续transform结构中会处理为近似为0的权重。其实我觉得,只要是专门的填充字符,不用专门做这个权重处理吧。
延伸2:
有没有看到strawberries被切开了,这里可以看下bert源码WordpieceTokenizer:This uses a greedy longest-match-first algorithm to perform tokenization using the given vocabulary.
input = “unaffable”
output = [“un”, “##aff”, “##able”]
简单来说,有些词不存在词典vocab里(OOV问题),那么切开匹配,怎么切,longest-match-first algorithm
延伸3:
那么词典怎么生成,当然对应英文单词来说空格切开,中文就是一个汉字,但是还记得延伸1中的"##aff"这种么,这就涉及到Subword策略,可阅读链接https://plmsmile.github.io/2017/10/19/subword-units/进阶了解,这种策略的好处就是减小词典、一定程度解决OOV问题。那为什么英文情况下词典不直接就用26个英文字母呢,我觉得理论上效果应该没问题,可是这样变成token序列之后就很长了,效率降低很多吧?
延伸4:
源码里的一些小trick,比如去除利用unicode码来判断是否是中文、检测控制字符(’\n’这种)、大小写是否归一。
延伸5:
感觉那个Segment向量理论上是不是可以不要啊,[SEP]貌似隐式的确定了它们的边界。对于分类任务,[CLS]对应的transformer后面的向量可以被看成 “sentence vector”,Fine-Tuning之后才有意义。
延伸6:
相加以后的向量还要经过一个layer_norm_and_dropout,对最后一维做norm,即只对embedding_size这一维做norm,这个好理解,原本3组向量假设都是均值0标准差1的截断正态分布初始化,那么相加以后标准差应该是3\sqrt{3}3,normalization下合情合理;关于这个dropout,没事drop一下房过拟合?
1.1.4 至此,任何一句话/一个样本都变成了一个n * m的向量/tensor输入模型,n表示多少个token,m表示embedding的维度,可以进入后续模型了
1.2 再看模型结构:中间的encode层,transformer
首先啰嗦下,经过1.1的处理,文本/字符串序列数据被转化为一个n * m的向量/tensor,n表示多少个token,m表示embedding的维度。
接下来transformer:
其实很简单,self-attention嘛,来,建议边看边在草稿纸上画写,应该很轻松。
1.2.1 step1:
上面的例子,输入为一个6*128维的向量(6个128维向量) [vec1,vec2,vec3,vec4,vec5,vec6][vec_1,vec_2,vec_3,vec_4,vec_5,vec_6][vec1,vec2,vec3,vec4,vec5,vec6] ,然后对于每一个128向量设置一个对应3个全连接层 f1−q、f1−k、f1−vf_{1-q}、f_{1-k}、f_{1-v}f1−q、f1−k、f1−v(带激活函数) ,暂且都设置(这不影响理解)为128 * hidden_size大小,假设hidden_size为512,那么每个128维向量都可以映射为3个512维度的向量,即得到[(q1,k1,v1),(q2,k2,v2),(q3,k3,v3),(q4,k4,v4),(q5,k5,v5),(q6,k6,v6)][(q_1,k_1,v_1),(q_2,k_2,v_2),(q_3,k_3,v_3),(q_4,k_4,v_4),(q_5,k_5,v_5),(q_6,k_6,v_6)][(q1,k1,v1),(q2,k2,v2),(q3,k3,v3),(q4,k4,v4),(q5,k5,v5),(q6,k6,v6)]
然后就是一个公式 Attention(Q,K,V)=softmax(Q∗KT/dk)∗VAttention(Q,K,V)=softmax(Q*K^T/\sqrt{d_k})*VAttention(Q,K,V)=softmax(Q∗KT/dk)∗V
1.2.2 step2:
我知道这个公式有的人看起来难受,继续那上面的例子来画,用q1q_1q1分别与k1、k2、k3、k4、k5、k6k_1、k_2、k_3、k_4、k_5、k_6k1、k2、k3、k4、k5、k6向量点乘得到一个6维数组,每个元素为浮点数,将6维浮点数向量除以dk\sqrt{d_k}dk即向量kkk的维度(这里就是512)的平方根,然后softamx(带dropout,这里有细节见后面的进阶延伸1),得到一个6维向量[a1,a2,a3,a4,a5,a6][a_1,a_2,a_3,a_4,a_5,a_6][a1,a2,a3,a4,a5,a6],然后vf1−1=a1∗v1+a2∗v2+a3∗v3+a4∗v4+a5∗v5+a6∗v6v_{f1-1} = a_1*v_1+a_2*v_2+a_3*v_3+a_4*v_4+a_5*v_5+a_6*v_6vf1−1=a1∗v1+a2∗v2+a3∗v3+a4∗v4+a5∗v5+a6∗v6得到一个512维度的向量vf1−1v_{f1-1}vf1−1,与vec1vec_1vec1对应;同理得到512维度的vf1−2、vf1−3、vf1−4、vf1−5、vf1−6v_{f1-2}、v_{f1-3}、v_{f1-4}、v_{f1-5}、v_{f1-6}vf1−2、vf1−3、vf1−4、vf1−5、vf1−6与vec2、vec3、vec4、vec5、vec6vec_2、vec_3、vec_4、vec_5、vec_6vec2、vec3、vec4、vec5、vec6对应。
1.2.3 step3:
整理一下,上述输入为一个6*128维的向量[vec1,vec2,vec3,vec4,vec5,vec6][vec_1,vec_2,vec_3,vec_4,vec_5,vec_6][vec1,vec2,vec3,vec4,vec5,vec6],经过这个套路可以得到一个6*512维度的向量[vf1−1、vf1−2、vf1−3、vf1−4、vf1−5、vf1−6][v_{f1-1}、v_{f1-2}、v_{f1-3}、v_{f1-4}、v_{f1-5}、v_{f1-6}][vf1−1、vf1−2、vf1−3、vf1−4、vf1−5、vf1−6],这就是self-Attention,不是self的可以延伸想一下,并不难。
1.2.4 step4:
multihead,很简单。
上述1组全连接层为f1q、f1k、f1vf_{1q}、f_{1k}、f_{1v}f1q、f1k、f1v,将[vec1,vec2,vec3,vec4,vec5,vec6][vec_1,vec_2,vec_3,vec_4,vec_5,vec_6][vec1,vec2,vec3,vec4,vec5,vec6]转化为[vf1−1、vf1−2、vf1−3、vf1−4、vf1−5、vf1−6][v_{f1-1}、v_{f1-2}、v_{f1-3}、v_{f1-4}、v_{f1-5}、v_{f1-6}][vf1−1、vf1−2、vf1−3、vf1−4、vf1−5、vf1−6],那么对于另一组全连接层f2q、f2k、f2vf_{2q}、f_{2k}、f_{2v}f2q、f2k、f2v同理可得到[vf2−1、vf2−2、vf2−3、vf2−4、vf2−5、vf2−6][v_{f2-1}、v_{f2-2}、v_{f2-3}、v_{f2-4}、v_{f2-5}、v_{f2-6}][vf2−1、vf2−2、vf2−3、vf2−4、vf2−5、vf2−6],以此类推…
有多少组全连接层,就是多少个head。
1.2.5 step5:
假设10 heads,即10组全连接层,那么可以得到10组6*512维度的向量,将对应位置的向量concat得到,得到一个6*5120维度的向量,即将上述的vf1−1、vf2−1、vf3−1、...、vf10−1v_{f1-1}、v_{f2-1}、v_{f3-1}、...、v_{f10-1}vf1−1、vf2−1、vf3−1、...、vf10−1concat为一个5120向量与vec1vec_1vec1对应
1.2.6 step6:
输入一个6*128维的向量经过得到一个10 heads attention操作得到6*5120维度的向量,然后经过一个全连接层fsf_sfs 大小为 5120 * 128(带dropout、layernorm、residential之类),映射为一个6*128维的向量,当然我这里简化,实际上你可以多来几层全连接啥啥啥的,这不重要。
重要的是,你可以看到输入一个6*128维的向量,经过一系列骚操作得到一个6*128维的向量,那把这套骚操作复制下去不就行了,那就是transformer的层数,比如bert精简版好像是12层。
1.2.7 进阶延伸1
对于mask掉的位置和长度不足pad的位置,在上文进入softmax之前,会把对应的位置那个点乘浮点数置换为一个很大的负数,以至于softmax之后接近0,这个很好理解啦,就是这个位置的信息被mask掉。这个也和预训练的设置有关系。
1.2.8 至此:一个6 * 128维的向量经过多头、多层的self-attention转变为一个6*128维的向量,整体上不过是一个encoder而已。当然hidden-size什么的维度问题自己控制就好,毕竟加个全连接层可以轻松的变换向量维度(size)。
1.3 看完模型输入、结构,再看loss怎么构建的
fine-tune的时候和具体的任务相关,此处以pre-train和文本分类fine-tune为例介绍:
1.3.1 pre-train的loss:本质上多分类loss+2分类loss
预训练的loss = mask词的预测loss1 + 两句话是否是上下文预测 loss2
mask词的预测loss1:
step1:因为都是矩阵操作,所以源码对新同学看起来可能有点吃力,我这里简要描述下。上文transformer将输入的6 * 128维的向量encoder为6 * 128维,再经过一个全连接层,将维度转化为和词表的embedding size一样,比如这里的全连接层为128*128,由此得到6 * 128维度向量(你可能觉得这里多此一举,实际上当transformer输出不是128维,是1280维的时候,这个全连接层就有用了)
step2:假设6个token的第2个词和第4个词被mask掉了,那么需要对这两个地方的token进行预测loss计算。对于第二个token位置,将step1得到6*128向量的第二个128维向量与词表中每一个词的embedding向量做点乘(这就是step1全连接层的目的),加上一个bais,(假设词典有50000个)这个时候会得到一个50000维度的实数向量,然后softmax,然后与一个50000维的0、1向量点乘(1的位置代表mask掉的词在词典中的索引),得到loss,取负号(预测越准,loss越小)。举个例子其实很简单,假设softmax之后的向量如下[0.001, 0.003, 0.009, 0.012, 0.0001,…0.023](50000维),上文被mask掉的第二个词在词典vocab中位置是第5个,那么这个0、1向量为[0, 0, 0, 0, 1, … ,0](50000维),对于第二个词的loss为-0.0001
step3:同理得到第4个词的预测loss,假设为-0.0003,两者取平均值得到-0.0002为这个样本的整体预测词loss。一个batch的loss,再对batch取个平均呗。
两句是否是上下文的预测loss2:
还记得第一个token是“[CLS]”么,和他对应的那个encoder之后的向量也就是那个输出6*128向量的第一个,然后接个全连接层(+bais),映射为一个2维向量,二分类问题,就不多说了。
1.3.2 loss解释
总的来说,很简单,对于mask词的loss,就是讲对应位置的encode 向量(上文是128维)做一个vocab size(上文是50000)的分类,取loss,然后对多个词、多个样本平均;对于是否是上下文,就是将“[CLS]”的encode 向量做一个2分类,求loss
1.3.2 文本多分类fine-tune的loss
参考两句是否是上下文的预测loss2,比如你的分类是20分类,将“[CLS]”的encode 向量映射为一个20维度向量,然后去取loss(交叉熵什么的都随意)
2.bert的其他
思考1:本质上是有监督loss优化,但是语料是不需要人工标注的,word2vec不也是这个逻辑么。
经验2:做fine-tune的时候,注意学习率的设置,很容易就飞了。显卡尽量大一点,模型不小,我用的2080ti,还行。
思考3:感觉bert这种mask预测、上下文预测构建损失、transformer结构之前其他论文多多少少都有,所以google这是将有用的套路放在一起,然后怼上计算力和庞大语料开干?
思考4:其实pre-train上finetune,比如你做的是豆瓣上评论相关的事情,是不是可以搞一批语料在pre-trained的bert基础上再pre-train一词,然后fine-tune具体的业务
先就到这把,感冒了发烧了。。。。
本文深入解析BERT模型的架构与工作原理,从预训练到fine-tune全过程,包括输入处理、transformer编码器、多头注意力机制及损失函数构建。通过实例说明如何将文本转化为向量,进行自我关注操作,以及如何通过预训练任务优化模型。
790

被折叠的 条评论
为什么被折叠?



