先开一个避坑指南的贴,慢慢更新。同时我号召各位大神踊跃留贴,分享一下自己的踩坑经历以及宝贵经验。给新入门的Paddle萌新们一些指导,让飞桨惠及更多的用户。
我同时也在AI Studio项目中发了一个相对应的项目,感兴趣的欢迎去看一看。链接:https://aistudio.baidu.com/aistudio/projectdetail/616307
静态图篇
我的静态图编程的步骤
我习惯于把自己想要实现的程序尽量整合到一个类里进行封装,这样在用的时候会方便一些。我用静态图编程一般是:
- __init__,自己定义;
- 定义模型训练参数的核心逻辑,即接收某个变量,然后输出想要的结果。这个单独写成一个函数;
- 定义训练模型和测试模型,分别单独写成函数,接收main_program和starup_program,返回结果(loss,accuracy等)在函数中用fluid.layers.data定义需要模型接收的参数;
- 定义一个数据batch生成器,把数据一个batch一个batch地送入1中的函数;
- 定义一个训练函数和测试函数,两个函数的主要区别是训练函数有损失后向传播,定义优化器等操作,而测试函数不需要这一部分;
- 调用这个类,用类的实例进行训练和测试。
静态图的坑
以下是我在静态图编程过程中遇到的坑以及暂时的解决方案。仅供参考:
- 第一个问题我觉得很多人可能都要遇到,就是用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实现 - 静态图篇
动态图篇
我的动态图编程的步骤
跟上边说的一样,我喜欢封装成类哈哈哈。。。
- __init__,注意继承fluid.dygraph.Layer
- 定义forward函数,类似于静态图中的步骤1;
- 定义训练函数和测试函数;
- 定义一个数据batch生成器,把数据一个batch一个batch地送入1中的函数;
- 调用实例,进行训练。
动态图的坑
- 自定义的层需要继承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应该有的样子。
其实,我这两天发现。这个bn的问题可能还和relu激活函数有关系。用sigmoid和tanh就没这个现象。用relu和leaky_relu就会出问题。
这个问题需要在测试集的数据预处理上下功夫
这是什么任务?
可以哈哈
是不是因为训练集和测试集的分布不一致造成的?我感觉可能是这个原因。
你这么一说,我发现我用的也是relu。
太玄了太玄了
可以
嗯,我还发现bn并不是每一层卷积都加效果最好。每次加bn其实都引入了噪声。
我又用其他数据集试了下,情况有所改善。
感觉越复杂的网络,对调参的要求越高。所以使用不当,复杂的网络性能是发挥不出来的
我正在用的模型去除了所有的bn层后,效果提升了,惊喜不惊喜,意外不意外。。。
我只在一个层中将所有参数按照bn层的思想搞了个归一化。其他的bn层全部删除,训练收敛速度和测试效果都提高了。。。
这个BN的效果似乎是一个倒U曲线~~
网上搜了搜,也没找到相关的说明缘由的文章
学习一下,谢谢分享
太棒了
这个很迷。加了bn层后我的模型速度变慢了好多。
跑的图模型?
NLP的吧?
我跑CV分类模型,有时bn层反而加剧过拟合