首页 Paddle框架 帖子详情
Paddle避坑指南
收藏
快速回复
Paddle框架 文章深度学习模型训练 29922 198
Paddle避坑指南
收藏
快速回复
Paddle框架 文章深度学习模型训练 29922 198

先开一个避坑指南的贴,慢慢更新。同时我号召各位大神踊跃留贴,分享一下自己的踩坑经历以及宝贵经验。给新入门的Paddle萌新们一些指导,让飞桨惠及更多的用户。

我同时也在AI Studio项目中发了一个相对应的项目,感兴趣的欢迎去看一看。链接:https://aistudio.baidu.com/aistudio/projectdetail/616307

静态图篇

我的静态图编程的步骤

我习惯于把自己想要实现的程序尽量整合到一个类里进行封装,这样在用的时候会方便一些。我用静态图编程一般是:

  1. __init__,自己定义;
  2. 定义模型训练参数的核心逻辑,即接收某个变量,然后输出想要的结果。这个单独写成一个函数;
  3. 定义训练模型和测试模型,分别单独写成函数,接收main_program和starup_program,返回结果(loss,accuracy等)在函数中用fluid.layers.data定义需要模型接收的参数;
  4. 定义一个数据batch生成器,把数据一个batch一个batch地送入1中的函数;
  5. 定义一个训练函数和测试函数,两个函数的主要区别是训练函数有损失后向传播,定义优化器等操作,而测试函数不需要这一部分;
  6. 调用这个类,用类的实例进行训练和测试。

静态图的坑

以下是我在静态图编程过程中遇到的坑以及暂时的解决方案。仅供参考:

  • 第一个问题我觉得很多人可能都要遇到,就是用fluid.layers.data定义数据类型时,如果自己需要定义了batch的大小,会莫名其妙出现数据格式不匹配的错误。这个错误虽然很容易debug出来,但是还是费了我很久的时间,因为压根就没往这上面考虑,一直以为是上述步骤1出了错(张量变换最容易出错这个大家没意见吧哈哈哈)。
    这个问题出现的原因是官方在fluid.layers.data中有个append_batch_size的选项,这个选项默认为True。当它检测不到有个维度值是-1的话,它就觉得好像没有定义batch这一维,然后它就“贴心地”给你加了一个batch_size=-1。但是batch_size已经给出来了啊。。。解决方法很简单,如果用这个函数定义数据,就记着定义append_batch_size。值得注意的是,fluid.data也可以完成相应的功能,但是没有append_batch_size这个选项。
  • 如果你用静态图,并且有查询词向量的需求,那么你大概率会碰到这个问题。当我期盼着期盼着把embedding训练完以后,想要看看自己训练embedding的结果,我调用embedding.numpy(),然后报错:请在fluid.dygraph.guard()的环境下运行这个。搞毛?我用的静态图,干嘛要用动态图保护啊。这个问题困扰我了好久,那时已经有“这还玩个毛线”的感觉,离弃坑就差那么一毫米的距离了。后来仔细研读了官方的实例教程,还是没有什么启示,这里希望官方可以出一个提取参数的应用。

    后来。。。我不得不承认自己是个傻子,这是静态图啊!取参数的时候是不是要run一下呢?一试果然行。后来才明白fetch_list真的可以包罗万象,你想要什么参数,尽管去试一试,只要有名字,就可能给你fetch出来。不过fetch会大大拖慢运行速度,如果是最终要得到某个参数,不要在train的过程中频繁的fetch,等到模型训练完,单独run一下去fetch就行了。
  • 关于fluid.Program()和fluid.default_main/startup-program()的使用问题,这里有很多坑。首先,官方例子中定义main=fluid.Program(), startup=fluid.Program(),应该是可以跑通的,但是我尝试的时候却出现了错误,提示没有初始化参数,但是Program不就是干这事的吗?于是我用main=fluid.default_main_program(), startup=fluid.default_startup_program(),没问题。需要注意的是,我定义了测试模块。而当我把test_main_program=fluid.default_main_program(), test_startup_program=test_startup_program(),完了,又错了,错误是重复定义了某个参数。而我把test_main_program和test_startup_program=Program()时,问题又消失了。这个问题可以看我的项目(https://aistudio.baidu.com/aistudio/projectdetail/592636) 。


    但是问题又出来了,我又写了一个项目,相同的套路,不灵了。我调来调去,把test_main_program和test_startup_program又写成default_main/startup_program(),又行了。。。我。。。


    不得不说,我现在也没搞懂为什么,有懂的大哥请提示一下我,万分感谢。
  • 这次是test_main_program.clone(for_test=True)这个命令该放到哪里合适,官方文档中的建议是放在exe.run(startup_program)之前,但是我刚开始并没有看官方文件,直接把这个和test_main_program以及test_startup_program都放在了测试函数中,也就是说等train运行完了,才开始创建这几个量。也安安静静用了好几天,并没有发现什么不对劲的问题。后来看了官方文档,给改回去了,不知道是不是错觉,改回去后测试的loss增加了那么一丢丢,不知道是不是心理原因,难道后定义,会继续训练?我测试模块甚至都没定义误差反向传播啊。。。这个也是个迷,望懂的大哥提示我一下,万分感谢。
  • 以上问题请参考这个项目https://aistudio.baidu.com/aistudio/projectdetail/641295 ,等我弄明白了再更新原因。
  • 2020/8/5 更新一个最新遇到的问题。有时候会遇到测试和训练用到的结构有改变的情况,比如为了解决OOV问题,Embedding层可能在测试的时候有所扩充,这样训练和测试用的是两个不同的Embedding。如果将两个Embedding层分开命名,会报出 LookupTableV20p should not be null 的错误。经过仔细debug得知,这个错误是不同名的新Embedding层无法接收传入的样本数据造成的。如果将两个Embedding层统一命名,lookup_table_v2 错误会消失,但是如果两个Embedding的尺寸不一样,虽然在调试中各个参数正常,但是仍然会报尺寸不匹配的错误。合并上面两个报错可以初步推断出,变量不是直接传递的,而是有更深层次的机制。所以,两个Embedding层不仅要名字相同,也要尺寸一致。详细情况参考项目NLP经典之八 - SkipThoughts的Paddle实现 - 静态图篇

动态图篇

我的动态图编程的步骤

跟上边说的一样,我喜欢封装成类哈哈哈。。。

  1. __init__,注意继承fluid.dygraph.Layer
  2. 定义forward函数,类似于静态图中的步骤1;
  3. 定义训练函数和测试函数;
  4. 定义一个数据batch生成器,把数据一个batch一个batch地送入1中的函数;
  5. 调用实例,进行训练。

动态图的坑

  • 自定义的层需要继承fluid.dygraph.Layer吗?不用。如果你定义了一个类来写你的模型,你模型中有一个层是自己定义的,那么你只要在模型中调用自定义的这个层就行了,模型需要继承dygraph.Layer;
  • with fluid.dygraph.guard()用在哪里?因为这个问题,我曾经一度怀疑动态图无法把训练和测试直接写到定义的模型中,因为我直接在train和test(valuate)函数中用with fluid.dygraph.guard(),调用类实例,用train训练后,各种报错,各式各样的错。



    然后我把train和test单独写出来,嘛事没有。后来我突然想试试with fluid.dygraph.guard()是不是要用在类实例上,于是我把train和test重新写入类中,然后先写with fluid.dygraph.guard(),然后调用类实例,用train训练,用test(evaluate)测试,跑通了。



    这样的问题是,如果你想把所有的工具都封装在一个类中,那么你要提醒用这个工具的人,在跑的时候莫忘了加上一句with fluid.dygraph.guard(),就像你去郊游带上自煮火锅,封面上还给你备注了请使用前带上煤气罐烧火。个人认为还有另外一个解决方法,就是麻烦点,把模型写到一个类里,然后把各种工具比如train,evaluate,predict等写到另外一个类里,然后这个类调用模型的那个类,用with fluid.dygraph.guard(),完美!就是有点费手!
  • 接下来就是这个梯度传播的问题了。首先,动态图中各个层的定义,用fluid.XXX而不是fluid.layers.XXX,这个最典型的就是全连接层fc,我在项目(https://aistudio.baidu.com/aistudio/projectdetail/550438) 中就因为这个layers.fc,在动态图中loss一直不收敛。后来在高人指点下,意识到了问题所在。动态图中请使用Linear来做全连接层。但是这个Linear有一个问题就是,在定义的时候必须指定input_dim这个参数,也就是说,输入的这个被拉通的向量有多大。这就很难受了,因为一般batch_size是不指定的,在运行的时候才知道有多大,那么这个被拉通的向量有多大事先是不知道的,又怎么给出呢?我在上述项目中给出了一个临时的解决方法,就是Linear这个层在训练时送入数据后才确定,然后就可以知道batch_size了。具体请看上述项目。这只是个临时的解决方式,也是被逼无奈。希望官方可以想办法去掉Linear对input_dim的要求,这样就方便多了。
  • 还是梯度传播问题。我在项目 (https://aistudio.baidu.com/aistudio/projectdetail/519590) 中遇到了一个问题,没有用全连接层,但是loss依然不收敛,看着明显就是没有被训练的样子。这是为什么呢?找了好久bug,最后在一个交流群里有个高手提醒我了一下,把所有训练参数打印出来看看,我才突然意识到,我是不是训练了所有参数。打印一看,果然,很多层的参数都没有。后来才发现,问题出在优化器定义的时机不对。我在with fluid.dygraph.guard()命令后调用模型的实例,调用后为了方便直接定义了优化器,因为优化器要用模型的parameters()去填充parameter_list。但是问题是,由于某些原因,一些没有在\_\_init\_\_中定义的参数,此时可能还没有定义,那么这时候调用parameters(),就不能得到所有的可训练参数,自然也就不会训练到所有参数。所以,为了避免这个问题,优化器的定义最好在forward被调用了一次后。我定义在loss的后向传播之后,因为这个时候要用到优化器了。
    另外,个人感觉优化器被调用一次,还是每个batch都调用一次的差别不大。所以优化器的定义可以直接写,也可以用判定条件让它只被定义一次。具体可以参考上述项目。
  • 2020/10/2更。最近又新发现了一个坑。当定义的一个类中需要包含一个列表,该列表中列举了一系列层,以依次输出其结果,比如下图的效果。直接定义一个列表来append这些层是不行的,parameters里面不会包含这些层的参数,因此训练也不会涉及这些层。此时需要把列表(list)定义为fluid.dygraph.LayerList(可能很多人已经知道了,我真是后知后觉啊。。。

其他小知识

  • 可以使用这个接口 fluid.contrib.model_stat.summary(fluid.default_main_program())来获知模型的概况:各层大小,形状,占用等。

    https://github.com/PaddlePaddle/Paddle/blob/release/1.8/python/paddle/fluid/contrib/model_stat.py#L40

总结

从两个月前首次接触AI Studio以来,从陌生到熟悉,感觉是又爱又恨。每个工具都有一个从入门到放弃的艰难历程,只是我们作为Paddle这个新生框架的早期用户,难免要摸着石头过河。这个历程虽然历尽艰辛,但是也不乏各种乐趣。希望飞桨这个平台越办越好,也希望更多的人能够加入到这个生态的建设中。更希望有朝一日,当我们使用这个框架时,遇到某个问题,只要一百度,就会有各种解决方法等着我们。这才是我理想的Paddle应该有的样子。

8
收藏
回复
全部评论(198)
时间顺序
学习委员
#2 回复于2020-07

好文绑定~

大家也可以在评论中分享自己踩过的坑以及填补方式哈哈

0
回复
没入门的研究生
#3 回复于2020-07

自顶一下!!

0
回复
AIStudio810258
#4 回复于2020-07

点赞!收藏!顶起来!

0
回复
AIStudio810260
#5 回复于2020-07

这么好的帖子咋不早点出现,现在还得回去复现bug哈哈

0
回复
AIStudio810260
#6 回复于2020-07

关于PaddleX的Detection模型库部署到移动端我遇到的坑,最新写了个项目做了部分整理,大家感兴趣的话也可以去看一看:

https://aistudio.baidu.com/aistudio/projectdetail/613622

1
回复
没入门的研究生
#7 回复于2020-07
这么好的帖子咋不早点出现,现在还得回去复现bug哈哈

哈哈哈,加油找bug!

0
回复
没入门的研究生
#8 回复于2020-07
关于PaddleX的Detection模型库部署到移动端我遇到的坑,最新写了个项目做了部分整理,大家感兴趣的话也可以去看一看: https://aistudio.baidu.com/aistudio/projectdetail/613622
展开

棒!!

0
回复
kj7541
#9 回复于2020-07

paddle用fluid.one_hot()的时候出现参数未初始化,请懂得大佬帮忙解决下

https://ai.baidu.com/forum/topic/show/960796

0
回复
没入门的研究生
#10 回复于2020-07
kj7541 #9
paddle用fluid.one_hot()的时候出现参数未初始化,请懂得大佬帮忙解决下 https://ai.baidu.com/forum/topic/show/960796

要不你也尝试一下把test的default_main_program和default_startup_program都换成Program

0
回复
thinc
#11 回复于2020-07

补充一下:

fluid.layers.data在以后的版本会改成fluid.data,前者不会检查输入数据的维度和类型是否符合要求,但是fluid.data在运行的时候执行器会去检查。万一碰到模型奇怪的错误,可能还不好debug

0
回复
thinc
#12 回复于2020-07

说下我的理解:

default_startup_program()是用来初始化参数了, 只允许一次即可,多次调用肯定出问题;

default_main_program()可以在train和test分别调用一次。

不过你可以试试只创建两个program分别为start和main,预测的时候clone一下就好了

train_program = fluid.Program()
startup_program = fluid.Program()

test_program = train_program.clone(for_test=True)
1
回复
学习委员
#13 回复于2020-07
这么好的帖子咋不早点出现,现在还得回去复现bug哈哈

哈哈哈哈

0
回复
学习委员
#14 回复于2020-07

大家以后遇到坑就都可以在这里留言

1、问题描述

2、Paddle issue提供的解决方式

3、自行解决的经验

在此分享解决方案的用户有奖励哈~

有问题的同学也可以留言,说不定哪个大佬就帮你答疑解惑了呢

0
回复
thinc
#15 回复于2020-07

 Paddle飞桨精选问题每日分享:
 https://ai.baidu.com/forum/topic/show/959243

随缘更新...

2
回复
没入门的研究生
#16 回复于2020-07
thinc #12
说下我的理解: default_startup_program()是用来初始化参数了, 只允许一次即可,多次调用肯定出问题; default_main_program()可以在train和test分别调用一次。 不过你可以试试只创建两个program分别为start和main,预测的时候clone一下就好了 [代码]
展开

原来如此!学习 了!

0
回复
没入门的研究生
#17 回复于2020-07
thinc #12
说下我的理解: default_startup_program()是用来初始化参数了, 只允许一次即可,多次调用肯定出问题; default_main_program()可以在train和test分别调用一次。 不过你可以试试只创建两个program分别为start和main,预测的时候clone一下就好了 [代码]
展开

试了,有用!

0
回复
没入门的研究生
#18 回复于2020-07
kj7541 #9
paddle用fluid.one_hot()的时候出现参数未初始化,请懂得大佬帮忙解决下 https://ai.baidu.com/forum/topic/show/960796

你可以看一下上面鹿鼎记肯定的回答,可能对你有帮助。

0
回复
夜夜夜
#19 回复于2020-07

感谢分享,好东西

0
回复
AIStudio810258
#20 回复于2020-07
这么好的帖子咋不早点出现,现在还得回去复现bug哈哈

这里是大家的“八哥复现营”~~

0
回复
没入门的研究生
#21 回复于2020-07
这里是大家的“八哥复现营”~~

八哥复现营是什么?

0
回复
需求/bug反馈?一键提issue告诉我们
发现bug?如果您知道修复办法,欢迎提pr直接参与建设飞桨~
在@后输入用户全名并按空格结束,可艾特全站任一用户