BERT 微调实战
预训练好的 BERT 已经读懂了语言,只要在它上面接一个小尾巴、再用少量数据训一会儿,就能解决真实任务。
一个通才,差一个出口
预训练给了我们一个语言「通才」:它的每一层都已学会从上下文里提取语义。可它会的还只是完形填空,并不直接回答「这条影评是好评还是差评」。
面对这样一个具体任务,我们不必从零训练一个新模型。更聪明的做法是:在通才之上 加一个很小的、任务专用的结构 ,再用少量任务数据「点拨」一下,让它学会把已有的理解能力 导向 这个任务。这就是 微调 。
微调:在预训练模型上做小幅调整
微调(fine-tuning) 的机制可以拆成两步:
- 在 BERT 顶部接一个 任务头(task head) ,也就是一个小小的、任务专用的输出层(往往就是一两层全连接)。
- 用 带标注 的任务数据,对「BERT + 任务头」 整体 做一次训练。这次训练用 很小 的学习率,因为预训练学到的权重已经很好,只需小幅调整,「微调」之名正源于此。
于是同一个预训练 BERT,配上不同的任务头、喂不同的任务数据,就能变成情感分类器、实体识别器、问答模型……这正是「一次预训练、多种下游」的威力。
[CLS]:预留的「整句代表位」
要做句子分类,得先把「整句话」浓缩成 一个 向量。BERT 为此预留了句首的 [CLS] 位。
[CLS] 本身不对应任何具体词,没有自带词义。在自注意力的层层计算中,它会不断从句子里的其他词收集信息,是 BERT 预留的一个聚合位。但要注意,未经微调的 [CLS] 向量并不是现成的好句向量;只有接上分类头、用任务数据微调之后,梯度才会把它 塑形 成适合该任务的整句摘要。到那时,把 [CLS] 向量接一个线性分类层,就是一个句子分类器。
同一个 BERT,换头就换任务
理解类任务五花八门,但它们共享 同一个 预训练 BERT,区别只在 取哪些位置的输出、接什么头 :
留意一个分野:单句 / 句对分类只用 [CLS] 这 一个 位置;序列标注和问答则要用到 每个 token 的输出。但底座 BERT 始终是同一个,只是任务头不同。
为什么微调要用很小的学习率
从零训练时,权重是随机初始化的「白纸」,需要较大的学习率快速走向有用的解。微调的处境恰好相反:
动手:端到端微调一个情感分类器
把流程跑通一遍。数据用公开的中文情感语料(如 ChnSentiCorp 影评 / 商品评论,标签为正面 / 负面)。分词、padding、组 batch 这些你都已经在更早的课程里做过,这里直接用配套分词器完成。
只用很少的标注数据、训练很短时间,准确率通常就能达到相当高的水平,这正是预训练底座的价值所在。
动手:量化预训练到底值多少
光说「预训练有用」不够,做个对照实验量化它。保持 网络结构和数据完全相同 ,只改一处,一组加载预训练权重再微调,另一组从随机初始化从头训练:
结果会很直观:微调组往往 收敛更快 、且 最终准确率更高 ,而从零训练组在同样的小数据上很难追上。这就是「复用预训练能力」带来的实打实的差距。
绿线为加载预训练后微调、橙线为从零随机初始化训练,两组的网络结构、数据与训练循环完全相同,唯一差别是权重来源。微调组第一轮就到 88.5%、三轮升到 93.1%;从零训练组从 54.9%(仅略高于 50% 随机猜测线)起步,三轮只爬到 64.2%,始终追不上。这段稳定的差距就是预训练本身的价值。数值为示意性运行结果,与上方代码打印的两条准确率一致。
编码器路线走到头,撞上一堵墙
到这里,编码器路线的故事讲完了:BERT 把「双向理解 + 预训练微调」做到极致,至今仍是文本分类、检索、命名实体识别等 理解类 任务的主力。
但它有一个天生的短板:
而生成,恰恰是解码器与生俱来的本事,它本就是为「逐词往下接」而设计的。如果把解码器单独拎出来,也照样做预训练,会得到什么?这是「GPT 与生成式预训练」要回答的问题。