原先的诗句生成模型仅仅利用了bert language model,而没有去利用诗句语料进行finetune,尽管language model生成的结果已经令人比较满意了。但是我们想知道在特定的数据集上进行finetune的话,结果是否能得到提升,于是这篇post主要完成这个工作:continue training。

pytorch 多卡分布式训练

关于分布式训练,通常可以使用DataLoader,这个wrapper可以方便使用多张卡,而进程只有一个,唯一的问题是这个方法只能满足一台计算机上GPU的通信,对需要使用多个机器,多个GPU的任务无能为力。

1
net = nn.DataParallel(net)

以上代码将整个网络分布到多个GPU上。pytorch定义的网络模型参数默认放在GPU 0上,所以dataparallel的时候,实质上是把训练参数从gpu靠背到其他的gpu上同时训练。此时dataloader加载数据的时候,batch_size需要设置成原来batch的n倍,n为gpu的数量。

如果我们要使用多个机器上的GPU,pytorch依然提供了办法:

  • torch.utils.parallel.DistributedDataParallel方法:与dataloader类似,用来实现多机多卡分布训练,他可实现在不同机器的多个模型拷贝之间的平均梯度
  • torch.utils.data.distributed.DistributedSampler 方法:在多机多卡的情况下,每个卡读取的数据显然是不同的,dataparallel的做法是直接将batch切分到不同的卡上。对于多机来说,直接进行数据传输将会耗费很多时间,于是使用distributedSampler,确保每一个dataloader只会load到整个数据集的一个特定子集,避免不同进程之间数据重复

使用方法

1
2
3
4
5
6
7
8
9
from torch.utils.data import Dataloader,Dataset
from torch.utils.data.distributed import DistributedSampler
from torch.nn.parallel import DistributedDataParallel

dataset = your_dataset()
datasample =DistributedSampler(dataset,num_replicas=world_size,rank = rank)
dataloader = Dataloader(dataset,batch_size=batch_size_per_gpu,sampler = datasampler)
model = your_model()
model = DistributedDataParallel(model,device_ids = [local_rank],output_device=local_rank)

在设置dataloader的batch-size的时候,只需要设置单卡的batch-size即可。world_size指进程总数,就是卡的数量,rank是进程编号,local_rank指本地序号。要想使用DistributedDataParallel就需要先完成多进程的初始化。

1
torch.distributed.init_process_group()

image-20191224122817322

image-20191224123807016

image-20191224123750444

image-20191224124839172

梯度积累

梯度累加的步骤如下:

  1. 获取loss,输入图像和标签,通过infer计算得到预测值,计算损失函数
  2. loss.backward() 反向传播,计算当前梯度
  3. 多次循环1-2,不清空梯度,使梯度累加在已有的梯度上
  4. 梯度累加了一定的次数以后,先optimizer.step()根据累计的梯度更新网络参数,然后通过optimizer.zero_grad() 清空过往的梯度,为下一波梯度累加做准备

梯度累加总结来说就是每次获取一个batch吼不清空梯度,而是累加到一定程度的时候去清空梯度,变相的相当于扩大了batchsize,同时可以避免计算多个损失函数时,存储多个计算图。

pytorch采样器(dataloader)

pytorch在加载数据的时候提供了一个sampler模块,这个模块用来对数据进行采样,常用的采样器有RandomSampler,当dataloader中shuffle的参数为true的时候,系统会自动调用这个采样器。dataloader默认使用的采样器为sequentialSampler,即按顺序来进行采样。sampler组织好数据的下标后,在dataloader中将数据取出来。

pytorch and apex

pytorch在分布式训练上存在着一些问题:

  • 混合精度训练难以收敛:pytorch可以方面得将模型转换成fp16,但是训练batchnorm层的时候,又需要转成f32,导致了混合精度,难以优化的问题。
  • bn同步的问题,bn同步能够极大的加快模型的收敛,精度也会有所提升,原生的方法一直未能较好地解决

apex是NVIDIA维护的一个支持

bert优化器

bert常使用的优化器有BertAdam,AdamW,FusedAdam(和BertAdam类似,当用到apex时配套当做优化器使用)。Bert的优化器和传统的Adam优化器有什么不同呢,主要的不同有以下两点:

  • bertAdam/AdamW 能够固定权重衰减,可用于微调模型
  • bertAdam/AdamW 不会对偏差bias进行补偿

bert loss function

bert损失函数主要由两部分组成,第一部分来自Mask-LM的单词级别分类任务,另一部分是句子级别的分类任务。通过联合学习,使bert学习到语言中token级别,以及句子级别的语义信息,具体的损失函数如下:

第一部分的损失函数是mask部分的词的一个多分类问题,词典的大小为分类的大小。第二部分的损失函数是是否是下一句的二分类问题,两个分类函数结合作为最后的loss.

bert激活函数:gelu

gelu:高斯误差线性单元,是一种高性能的神经网络激活函数,GELU的非线性变换是一种符合预期的随机正则变换方式:

image-20191224220030781

总结

总结一下上面的工作,我基本上了解了生成诗句的一套流程,对bert的结构也有了比较多的了解。总体来说,代码比较容易看懂,这比起C++的代码,简直轻松很多。

还可以梳理一下bert的脉络,研究一下whole word marking的实现方法。明后两天把这个事情做完。其他在按计划慢慢推进!