GPT2-Chinese icon indicating copy to clipboard operation
GPT2-Chinese copied to clipboard

训练过程的一些总结

Open movecpp opened this issue 5 years ago • 48 comments

一开始照搬模型设置训练了一个大型数据集,始终无法收敛到理想区间,又拿斗破来修改模型参数玩了个把星期,各种调参。

总结如下:

1. 模型的收敛取决于词嵌入的维度,维度越大收敛越快越好。(有没有上限就懒得去测试了,电费要紧。)

2.head与隐藏层数可以适当裁剪,隐藏层可以设置高一些,multi-head感觉超过5层之后似乎对于生成的结果影响并不大。

3. 模型长度不影响训练,但与学习效果有很大关联,能大些就大些。

4.训练效率问题,模型参数与训练效率息息相关,合理的batch数量、GPU显存、适当的模型参数(multi-head拉低,layer尽量比head大),100%使用率还是能达到的,当然了,小数据集随便跑吧,不用纠结,反正不费多少时间。

5. 鄙人发现,似乎参数与batch设置为双数,训练起来总有种飘飘欲仙的感觉,不知道是不是错觉。

一句话:

降低multi-head,适当保持layer层,embed越大越好,ctx,学习长度当然大了好了,数据集交叉学习收敛更快,最终生成效果更好,loss压到0.3以下,基本可以生成十分通顺的文章了。如何加快训练速度是个头痛的事,fp16精度损失实在太大,调整了很多很多次模型,始终是坐过山车,要么就不学习,最终只能放弃使用fp16。。。

movecpp avatar Dec 25 '19 19:12 movecpp

感谢总结!

Morizeyao avatar Dec 26 '19 04:12 Morizeyao

一开始照搬模型设置训练了一个大型数据集,始终无法收敛到理想区间,又拿斗破来修改模型参数玩了个把星期,各种调参。

总结如下:

1. 模型的收敛取决于词嵌入的维度,维度越大收敛越快越好。(有没有上限就懒得去测试了,电费要紧。)

2.head与隐藏层数可以适当裁剪,隐藏层可以设置高一些,multi-head感觉超过5层之后似乎对于生成的结果影响并不大。

3. 模型长度不影响训练,但与学习效果有很大关联,能大些就大些。

4.训练效率问题,模型参数与训练效率息息相关,合理的batch数量、GPU显存、适当的模型参数(multi-head拉低,layer尽量比head大),100%使用率还是能达到的,当然了,小数据集随便跑吧,不用纠结,反正不费多少时间。

5. 鄙人发现,似乎参数与batch设置为双数,训练起来总有种飘飘欲仙的感觉,不知道是不是错觉。

一句话:

降低multi-head,适当保持layer层,embed越大越好,ctx,学习长度当然大了好了,数据集交叉学习收敛更快,最终生成效果更好,loss压到0.3以下,基本可以生成十分通顺的文章了。如何加快训练速度是个头痛的事,fp16精度损失实在太大,调整了很多很多次模型,始终是坐过山车,要么就不学习,最终只能放弃使用fp16。。。

请问参数表能否分享一下?谢谢

kingmo888 avatar Dec 27 '19 19:12 kingmo888

@kingmo888 ctx:512 embed:800 head:10 layer:10 positions:512 参数表,怎么说呢,您可以自己尝试调整,ctx可长可短,短了epoch就多些,模型内存少一些,batch就能大一些,要根据自己显卡配置跟模型需求来。head跟layer小,训练效率就高,我单卡TITAN V改水冷,超频到极限(游戏不黑屏为标准),跑起来GPU利用率可以达到92以上,最高98,温度能达到80摄氏度,很暴力。词嵌入向量大一些好,不要超过1000,不要低于500,是比较适合的,超过1000虽然下降稍快一些,但提升很小开销太大,没价值。 LR不能大于0.0004,超过这个值后loss会在某个值震荡无法继续下降,我使用的最佳LR是0.0003,能够得到0.2的loss,是否能够再低,没有再做尝试。 想要达到显卡性能最大化,DataLoader是很重要的一个环节,prefetch_generator是必不可少的,我是将训练集直接加载到内存。prefetch_generator 包会自动在后台加载下一个step所需的数据,大家可以加缩进改目录地址直接用。为什么要self.char_len//2呢,比如ctx是1024,每次取1024个字符用作训练,那么对于上一个1024中的某些句子与下一个1024中的某些句子的关联就不能很好的学习到,所以,这样操作,第一个epoch,我的loss就能下降到4以下,梯度累加20个step,1.0的梯度裁剪上限,梯度裁剪上限不要调高,很容易过度学习,后期会在某个区间持续震荡。 另外,print操作对显卡利用率影响太大,本质上print属于IO操作,非常耗时,我是用jupyter训练的,livelossplot了解一下。 可能有些同学会问,我这个模型参数量已经很小了,最终生成效果是不是会有影响? 调参是很玄学的过程,本身就是黑箱操作,我追求的是最大化训练效率(相比于原有的train_single.py,训练速度能提升60%左右,显卡利用率高了。),参数量小,epoch也会加大,相对好训练些,每个人的配置不同,参数调整是无法直接复制黏贴使用的。 而且我已经反复训练超过300次了,不断调整参数,以最终生成质量来看,小模型未必不能生成高质量文章,取决于你的loss(但想要生成万字长文,ctx同样需要万字以上,否则超出模型长度之后的内容内容无法扩散(斗破为例,超出512长度后,不断的花式秀“异火”,句句不重样,句句是“异火”),loss再低都没用,这样应该能够明白吧?)。 目前已经上云训练我的26G超大数据集,4X2080Ti,模型参数有些变化,ctx加长到3072,head跟layer为6,embed是768,等训练好了再看看生成效果如何。 fp16,放弃吧,误差问题,loss就是过山车,O1O2都是这样,O3直接就是在玩耍,根本不是在学习。

以下是我的DataLoader: from prefetch_generator import BackgroundGenerator class MyDataset(Dataset): def init(self,num): self.char_len=num self.char=[] self.get_text() self.sector=0 def getitem(self, index): self.sector=(index+1)*self.char_len #data=Variable(torch.LongTensor(self.char[self.sector:self.sector+self.char_len])).to(device) data=Variable(torch.LongTensor(self.char[(self.sector-(self.char_len//2)):(self.sector+(self.char_len//2))])).to(device) #label=Variable(torch.LongTensor(self.char[(self.sector-(self.char_len//2)):(self.sector+(self.char_len//2))])).to(device) return data def get_text(self): for e in range(100): with open('E:/jupyter/gptch/data/tokenized/tokenized_train_{}.txt'.format(e), 'r') as f: self.char.extend([int(x) for x in f.read().replace('\n','').strip().split()]) #self.section=[n for n in [self.char[i:i+self.char_len] for i in range(0,len(self.char),self.char_len//2)] if len(n)==self.char_len] def len(self): return (len(self.char)-(self.char_len//2))//self.char_len def char_len(self): return len(self.char) class DataLoaderX(DataLoader): def iter(self): return BackgroundGenerator(super().iter()) train_loader = DataLoaderX(dataset=MyDataset(n_ctx),shuffle=True,pin_memory=False, batch_size=batch_size)

movecpp avatar Dec 28 '19 07:12 movecpp

还有就是,pretrain的话,要禁用掉动态学习率,否则loss不降反升。

movecpp avatar Dec 28 '19 07:12 movecpp

非常感谢太详细了!

walkingonthestreet [email protected]于2019年12月28日 周六15:14写道:

还有就是,pretrain的话,要禁用掉动态学习率,否则loss不降反升。

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Morizeyao/GPT2-Chinese/issues/119?email_source=notifications&email_token=AEEYKA4NFHXIEXTW2RLGW5LQ234GXA5CNFSM4J7GFEX2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEHYEHMA#issuecomment-569394096, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEEYKAZV7P3XYNNOGAO7H6LQ234GXANCNFSM4J7GFEXQ .

kingmo888 avatar Dec 28 '19 07:12 kingmo888

厉害~

Morizeyao avatar Dec 28 '19 07:12 Morizeyao

朋友们可以加一下交流群分享心得

Morizeyao avatar Dec 28 '19 07:12 Morizeyao

@walkingonthestreet 从训练速度来看,原版中的每个循环中,分好的每个文件都需要读一遍确实会慢一些。我是直接全部读到list[]里了,然后调用。内存大点所以没有迭代(说实话属于不会用pytorch的数据加载工具)。

整体来说,非大规模语料情况下数据加载不是瓶颈,主要还在于参数。哪些参数能够精简同时效果不下降这方面你给我很大的启发,我会测试一下,然后来汇报。

另外,数据的生成速度有切实可行的办法来提高吗?谢谢

kingmo888 avatar Dec 28 '19 14:12 kingmo888

是指tokenize的速度? huggingface正在开发加速版的bert tokenizer,据说tokenize速度可以加快约80倍。

Morizeyao avatar Dec 28 '19 14:12 Morizeyao

@Morizeyao 感谢作者冒泡。 我指的是generate.py的速度。 实测下来感觉sample_sequence、fast_sample_sequence速度差不多。1080Ti下长度七八百的话需要1分钟。大量测试的话,速度有点低。 如果在CPU下测试,会发现如果超过100字,后面会越来越慢,时间消耗是指数增长的。

kingmo888 avatar Dec 28 '19 14:12 kingmo888

@kingmo888 生成的话,你可以修改模型参数,改一个一次性预测N个词的,这样就快了。至于模型的后期部署优化,tensorrt 是一个,torchscript多核并行也行。或者训练完成后,切成fp16预测试试,fp16自然要比32快N倍的。

movecpp avatar Dec 28 '19 14:12 movecpp

@walkingonthestreet

想要达到显卡性能最大化,DataLoader是很重要的一个环节,prefetch_generator是必不可少的,我是将训练集直接加载到内存。

我在某云测试了一下,采用原版的方式,其GPU的利用率最低94,最高100,基本维持在98,而后我改用你说的prefetch_generator这个预先加载库,效果一样啊,因为利用率已经接近100,同样采用print打印信息,利用率依旧接近100。 因为原版的数据已经处理成数字索引,训练时就是一个加载问题,速度很快的,文件放在SSD上,测试时我是单文件(38M),而如果采用Dataloader,其原理跟原版差不多,最大的区别在于文件大小,假设训练一个10几M的,分成100个文件,最大的开销在于文件的打开以及系统与用户空间多次复制有关。那么10几M没有理由分成100个文件啊,最多就一个文件。即使不使用SSD,差距不会那么大吧。 请问你得出此结论时,原版的GPU的利用率是多少?

zhangxinqila avatar Dec 30 '19 01:12 zhangxinqila

@zhangxinqila 你用的多GPU训练吧?极客云?我的是本机训练,单卡,windows。无论是原版还是DataLoader加载方式,数据集都是一次性读取到内存中的,所以不存在IO耗时的问题。nvsmi是每秒统计一次数据的,算力强大的话,基本上波动不是很大,我用的AMD处理器,估计是内存延时问题吧。 我刚开始把玩这件宝物的时候,利用率仅65%左右,现在基本98%。 print问题,一般占用几毫秒吧,个别机器十来毫秒,只要不是每一个step都print一下没什么影响的。

movecpp avatar Dec 30 '19 13:12 movecpp

@walkingonthestreet 你猜对了,不过我是单GPU,你的数据也是一次加载的话,那么采用DataLoader或者原版方式,应该不是影响利用率的主要原因,DataLoader加载数据的方式与原版的处理相似,虽然prefetch_generator作了预加载,但是数据量不大而且只是加载而已,优势没有体现出来。GPU利用率跟其它参数也有关,如果模型本身计算量没有那么大,利用率也会不高的,个人见解,有误轻喷!nvsmi -lms毫秒

zhangxinqila avatar Dec 30 '19 14:12 zhangxinqila

@kingmo888 做了个测试,生成600个字,外加输入的10多个字,总600多一点,模型步长是1500,5层,大小267m。 随机抽样:使用了topk=10 topp=0 +torch.multinomial函数 原始: 使用了torch.topk函数

组合 CPU GPU
随机抽样 21m53s 3m59s
随机抽样+fast 1m6s 31s
原始 11m9s 2m8s
原始+fast 28s 27s

以上所有只做了一次测试,时间是由trange直接得到的

zhangxinqila avatar Dec 31 '19 13:12 zhangxinqila

@zhangxinqila 你5层的生成效果怎么样?词向量是多大?

movecpp avatar Dec 31 '19 14:12 movecpp

@walkingonthestreet 感觉没什么逻辑,小说是爬来的,避免有可能的麻烦,还是不展示的好。

zhangxinqila avatar Dec 31 '19 14:12 zhangxinqila

@zhangxinqila 是的,无论loss能够压低到什么程度,感觉gpt-2对于中文的学习能力很差,不知道是不是生成方式的不同所致,还是压根就无法学习中文这门复杂度极高的语言(英文的来来去去都是那26个字母,汉字数以万计)。像AIDungeon这样的项目,生成的结果是比较完美的,也许可以参考一下,这个项目的模型达到了5.8G,堪称巨大。

movecpp avatar Dec 31 '19 14:12 movecpp

@walkingonthestreet AIDungeon是引导型的吧,弥补模型没有逻辑的缺点,训练的时候应该需要提取引导句+续写内容的方式。模型越大,理论上能够记录的信息越多,表现也越好,所以目前都有向超大模型发展的趋势,没有资源跟他们玩。或许中文也可以试试,先小语料训练有关某个方面的模型,然后测试,如果可行再向大语料发展,反正现在感觉逻辑性是一个无法解决的问题。退而求其次,引导型辅助AI也是不错的尝试。

zhangxinqila avatar Dec 31 '19 15:12 zhangxinqila

@zhangxinqila 不是gpt-2不能学习中文,而是相比英文的26个字符而言,中文字数太多了,相应的模型必定是要以天文数字的参数量来训练的。貌似目前还没看到几个语言逻辑上过的去的中文模型,反观英语,26个字符跟汉字作品相比简直是九牛一毛,想起来就有点绝望,世界上唯一一个能够训练中文gpt-2的,恐怕只有强制绿的老黄了。。。

movecpp avatar Dec 31 '19 15:12 movecpp

@zhangxinqila 最新的reformer有没有研究一下老铁?

movecpp avatar Dec 31 '19 15:12 movecpp

@walkingonthestreet 没有研究过,中文的相关实践结论太少了,基本都是他国的实践成果,每一个都要从0摸索,普通的个人实践能力又有限,没办法。 又试了几次生成,可以生成原文,一字不差,上千字,不过需要跟训练时传递的语料一致,感觉loss0.5以下的,只要传递跟训练时一致的语料,输出原文应该不是问题,当然要注意,不要使用随机抽样生成结果,使用topk。 另外证明你的一个结论:超过步长,模型就天马行空。这很奇怪,使用了移动窗口,不应该是这样子的。另外,训练时,一开始不要使用动态学习速率也是对的,谢谢! ----------分割线--------------- 我想我知道原因了,其实跟步长没有关系,而是跟上文有关(输入) Transformer的注意层,三个权重矩阵Q,K,V,一旦模型训练完毕后,那么这三个矩阵也确定了,那么其对上文的要求也确定了。 如训练这么一句话:“现在测试GPT-2生成效【果】”,让模型预测【果】字,假设训练时模型正确预测到了。这时候,就要知道整个上文中主要贡献词是哪些?比如【测试】与【生成】贡献最大,那么生成时我们也要提供这两个词,那么模型就会正确生成果字,而由于GPT-2是采用自回归逐字生成,那么同理,想生成逻辑可读的段落甚至文章(跟训练时一致),就要重复这个过程,既上文要包含训练时起到关键贡献的词。否则模型就只能根据上文提供的干扰词来生成下一个词,以此类推,最终乱上加乱。 综上:如果想生成逻辑可读,上文不必与训练时的语料完全一致,但是需要包含训练时起到重要作用的词。

zhangxinqila avatar Jan 01 '20 12:01 zhangxinqila

@zhangxinqila 厉害,感谢分享。 其实我也尝试过,topk为1时,上文一样,生成的下文就是固定的,虽然比原来狗屁不通的下文好上一些,但逻辑上还是无法达到比较完美的状态。不管是禁用随机抽样,还是固定pytorch的random seed,貌似都差不多,实在是打击了研究下去的动力。 gpt-2的缺陷也很明显了,在gpt-2面前,只有强制绿的老黄是站在鄙视链顶端的男人了。 拜读了一下reformer的论文,有点啃天书的感觉,作者使用局部敏感哈希的想法恐怕也是来自于此,未读reformer时,也有过使用DHash验证上下文的想法,只是算力捉鸡,实在无力折腾。reformer一出,自己动手改gpt-2就没必要了,坐等大神更新算了。 reformer一出,分分钟就是万字级别的模型长度,论训练速度,能甩gpt-2好几条街,不过,原型的生成质量,恐怕也不比gpt-2好的上多少。 有个老外搬砖的一个半成品的Reformer pytorch版本,看样子快完成了,https://github.com/zbloss/reformer。

movecpp avatar Jan 01 '20 21:01 movecpp

又想到几个问题 不知道大家有没有尝试过分析以下问题: 前提条件:模型loss在0.5以下,或者凭经验觉得已经足够低了 1 生成原文测试,如果loss足够低的话,理论上可以生成原文的,那么输入的前文需要多长才能做到? 2 能够一字不差的生成原文,那么进行增、删、改,是否会影响结果? 如果能弄明白上面两个问题,接下来训练新的模型时,就考查模型的容差能力了,比如输入某些前文(语料中有的),但是并不完全相同,而模型同样可以续写后面对应的内容,由于这些内容是模型记录的原文,那么逻辑可读性应该不会差。那么我们就可以理解为:模型理解了我们的输入,最终给出了合理的结果。读到这里,可能会想到,容错(过拟合)?

关于效果 有的会说,生成原文的东西有什么用,如果想让模型跟我们一样:这个难度有多大呢? 作为人,我们也是术业有专攻的,没有学过的东西,让我们讲,我们也会磕磕绊绊的,脑子断路那更是常事。回想我们写文章或者论文,其中必有阅读收集资料的过程,对于模型来说,就是训练的过程。但是我们可以在阅读的过程中,收集想法并整理概要,最终将资料(原文)整合,论述表达一个核心观点,这可不是一次就写好的,我们要不断来回修改。而GPT-2只关注前文,完全像人一样写作,这太难了,而它能做的就是从训练过的原文来挑选相关联的东西。 所以我觉得辅助写作是比较好的实践方向,核心观点由我们构建,内容由模型根据输入续写,即引导型AI。当然会有生成指定题材的AI出现,但是效果也很难达到人写的高度,不过一些结构固定的题材,应该可以达到吧,但是像小说这种高度自由的题材,难度真的太大了。 纯属个人观点,有误轻喷! @walkingonthestreet 给的链接,有时间实践一下,非常感谢!

zhangxinqila avatar Jan 02 '20 03:01 zhangxinqila

大体上通顺还是能够做到的,只是生成模块需要整改整改

movecpp avatar Jan 02 '20 04:01 movecpp

@walkingonthestreet 你没有去尝试你发的这个库 https://github.com/zbloss/reformer 我试了一下,存在Bug,如果你感兴趣的话,可以去试试吧 https://github.com/zbloss/reformer_lm/blob/cf368b80cf50f177a17ef783d40d550175ecc141/reformer_lm/decoder.py#L45 在这行,shape就不对,如果head=1,可以跑,但是如果head不为1的话,就错了 https://github.com/zbloss/reformer_lm/blob/cf368b80cf50f177a17ef783d40d550175ecc141/reformer_lm/broadcasted_dropout.py#L31 这一行,直接挂掉,然后就没有然后了,没有任何出错信息。 另外参数问题: d_in与d_out参数(隐藏层输入与输出),我们一般设置一样的,而这个库,别出心裁,设置成不一样,一开始我还以为d_in是序列长度,d_out是维度,结果max_len才是对应GPT2的n_ctx。 然后我读了源码,感觉这个库跟原版的Tenforflow版本有点出入,不想再跟了,不然又得学会Tenforflow了,学会了我就不用pytorch了,毕竟现在Tenforflow2.0也不错啊,而且可以捡现成的。

zhangxinqila avatar Jan 08 '20 08:01 zhangxinqila

@zhangxinqila 没注意,今晚拿来把玩一下看看。

movecpp avatar Jan 09 '20 08:01 movecpp

@Morizeyao @walkingonthestreet @kingmo888 大家有没有办法提高模型的泛化能力啊,现在感觉输出原文是没有问题的,但是前文要求要与训练时传递的文本相一致,这个就有点老大难了,更不用说那些没有训练过的文本作为前文了。但是一想到GPT2使用的是Transformer来抽取特征,特别是Masked Self Attention这个机制,就觉得几乎无解,除非训练时就对前文作为特殊限定,否则生成时很难做到与训练一致。

zhangxinqila avatar Jan 09 '20 10:01 zhangxinqila

@Morizeyao @walkingonthestreet @kingmo888 大家有没有办法提高模型的泛化能力啊,现在感觉输出原文是没有问题的,但是前文要求要与训练时传递的文本相一致,这个就有点老大难了,更不用说那些没有训练过的文本作为前文了。但是一想到GPT2使用的是Transformer来抽取特征,特别是Masked Self Attention这个机制,就觉得几乎无解,除非训练时就对前文作为特殊限定,否则生成时很难做到与训练一致。 超大型训练集,大dropout,长context,除此之外,暂时没有什么好办法,除非自己改模型。但不管怎么说,超大参数模型似乎是必走的路。咱们不是老黄,没有强制绿的超能力,恐怕只能等待大牛放出新模型了。

movecpp avatar Jan 09 '20 10:01 movecpp

哥们,大dropout,模型有可能难以收敛啊,丢掉太多特征,输入上有区别,模型一dropout,啥都一样了。一说到长Context,我就想到TransformerXL,也看过一个改进深度网络泛化能力的方法,就是大数据,大模型,无从不包,能想到的都已经训练过了,泛化能力自然提高。但是作为个人无资源,只能说牛逼。。。 很好奇,你多次提到的老黄是谁,我去膜拜一下。

zhangxinqila avatar Jan 09 '20 10:01 zhangxinqila