先开一个避坑指南的贴,慢慢更新。同时我号召各位大神踊跃留贴,分享一下自己的踩坑经历以及宝贵经验。给新入门的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层后(加在激活函数前后都试过了),训练集能够快速收敛,但是导致验证集基本不收敛了。
后来改了网络结构就就好了。但还是疑惑这是什么现象?
或者能通过什么方法能够比较直观快速的看到问题所在?
卷积层能打印下参数看看,bn层怎么debug啊?
bug复现营
脑回路蹦太远了~~
学习
bug讨论专区
改后的网络结构是更复杂了还是更简单了?bn在简单网络上的效果不太好
哦哦哦,哈哈,这名字不错。
这个会不会是bn层在验证上没起作用引起的?
不错哦
PaddleX和PaddleDetection的num_epochs等参数设置完全不一样啊,有什么对照关系吗,求解惑。PaddleDetection上训练迁移学习模型MAP有0.4,PaddleX上MAP0.0003。。。
其实是把卷积核从3改成5了
还相应减少了层数。改的地方挺多的。
改之前,4、5轮训练集准确率就七八十了,而验证集卡在17%
什么意思?前向计算过程是共用的。为什么会在验证集上没起作用?
刚才看“世界人工智能大会”的demo day活动里面介绍了PySnooper工具,感觉挺好用的,要是Paddle也有就方便了。
验证的时候数据没有预处理也是一种可能 因为你训练有bn 出来的参数还是要记录下作为验证时候参考
你用VGG-19看下和原来有没有区别,如果卷积次数一样 55能提到更多特征,肯定能拟合更复杂的问题
调一下lr看看
我用tensorflow时遇到过bn效果不好的问题。bn在训练的时候会适应两个参数,mean和var。测试的时候这两个参数不需要训练。但是有时候bn在训练的时候强制设为不可训练反而效果好一点。不知道Paddle上是否有这个问题。
其实我还遇到了另一个问题,就是网络超过9层,性能就开始下降了。输入是224×224的图片。直接conv_pool大概6次后将7×7的特征图进行全局池化的一个模型,采用的是5×5的卷积核。然后,我用两个3×3的代替一个5×5的卷积层,发现层数超过9,准确率就开始下降了。所以我用的经典模型里(alexnet,vgg,googlenet),效果最好的竟然是alexnet
过两天我会公开,有空可以帮我瞅瞅
感谢,这是个很好的思路。我去试试