加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Pytorch和mxnet的简单比较(三):梯度及梯度清零

(2017-02-06 22:06:50)
标签:

pytorch

mxnet

grad_array

grad_dict

梯度

mxnet的自动求导有两个选项,分别是wrtie和add;pytorch则完全没有这个选项。一时间也不知道到底是个啥情况。然后试着在mxnet中用了这两个选项,算是明白了一点write和add的差别:write相当于梯度清零,而add相当于累加。所谓清零就是梯度重算,将上次计算的梯度置零,或者用新的梯度覆盖原值的操作。但是在LSTM中,是一轮之后write,还是在每次W、U之后write呢?当时,不论是哪种情况,mxnet的结果都是错误的。怎么回事呢?接着往下看。
Mxnet没找到感觉,那就在pytorch上试着找吧。好在pytorch非常容易上手,加之mxnet也有那么一点点的经验积蓄,因此很快就改写出了pytorch版本的LSTM。只是结果仍然是错误的——积蓄投资失败了。
接下来就是无数次的折腾。宅并折腾了两天,实在没头绪,便出门开了两个小时的车,算是彻底把mxnet和pytorch扔在了脑后。回到家刚一坐下来,突然想到write和add这两个家伙,便想pytorch没有这两个选项,能不能“人造”一个呢?在ipython互动环境里试了几个操作,发现pytorch的梯度是可以从“外部人为”赋值的。比如对某个Variable通过backward()求得梯度后,可以将其赋予另外的一个浮点数。这倒是有点费解,原以为只能通过backward()算法本身赋值的。当时也来不及细想,便直接在pytorch里面尝试结果如何。在进入每轮前后向训练之前,将下面9个矩阵的梯度重置为零:
V.grad.data.zero_()
Ux.grad.data.zero_()
Wx.grad.data.zero_()
V是最后的输出矩阵,Ux是输入矩阵,Wx是隐层矩阵。x是脚标,分别代表LSTM的f、i、g、o 四个gate。总共是9个矩阵,所以对9个矩阵的梯度清零。
接下来,就是正确的结果。
就这么个东西,折腾人啊。还是开车才能彻底抛弃一切啊!
回过头来尝试mxnet,不管是梯度的write还是add, 仍然是当初那一副屌样,错误的结果依旧。怎么回事呢?其实想想也没什么别的改变,不过是重复之前已经试过的write和add项目,当然不会有什么好的果子了。
既然pytorch的梯度可以人为模拟类似mxnet梯度write和add操作,那为什么mxnet的LSTM总是不对呢?问题肯定不在梯度本身上,而是别的什么问题导致了错误。问题还是继续留到下文再说,现在仍然讨论梯度问题。
后面的内容跟pytorch几乎再无关系,而是集中在mxnet上,只是免不了会把pytorch做面对照的镜子。除了梯度清零的问题,pytorch在使用时好像真的没有别的什么问题了。可见pytorch的框架的确比较照顾半生不熟者,其实应该理解为pytorch源自于torch,后者已经进化了多年,资金、团队、技术构成的整个平台非常成熟。
Mxnet的是否对变量求梯度的选项,只能在backwar()中做出选择,并且不能有针对性的选择。比如LSTM,我们实际需要的是Ux、Wx、V这9个变量的梯度【Ux,Wx的下标x代表f,I,g,o四个gate门】,但是mxnet不能选择,所以会把mnist28*28的28个输入和ht,ct,以及one_hot输入都通通求一次梯度。虽然从最后的比较结果看,这似乎也没有什么影响,或者不管是哪个框架都会对所有的输入变量求梯度,但是pytorch可以人为选择求梯度的变量,从字面上看要简洁易懂很多。
问题不仅仅在这里,mxnet的梯度求解,在格式上还是更为复杂一些。当然想通了也就没有什么复杂的地方,但是对初试者而言,运气不好的话,还是会碰一鼻子灰的。
在小示例中,比如y=x^2的求导,仅仅涉及一个变量,即便这个变量是矩阵,从逻辑上看,也是一个变量。如果是两个,立马就变得复杂了【待会儿讲】,如果遇上LSTM这个结构,就是28+9+2+1,至少40个变量【其实还有一个,共41个】,这种情况会更为复杂。
为什么复杂呢?mxnet梯度的选项有数组grad_array和字典grad_dic两种方式。作为初试者,往往会选择grad_dict方式来放置梯度。因为字典的结构特性,梯度变量会有个对应的名字。通过名字来把梯度变量放在backward()中的是很自然的想法。
两个变量好办,多了呢?作为初试者,谁知道会有几个呢?总之先试试两个总没错吧。问题来了,两个变量在backward()括号中的顺序居然是有严格要求的!!运气好放对了,则没问题;否则,潮水般的错误信息铺天盖地的涌来,有那么一瞬间,我感觉电脑似乎都要崩溃了。是些什么错误信息呢?我到现在也没去看,连复制粘贴拷贝的想法都没有过,何必跟尚不成熟的软件较劲呢。总之那里面最有价值的信息是知道executor.backward()这条语句报错。
原来,mxnet的梯度讲究位次,谁排第一,谁排第二还不是随随便便的事情。这倒是有点意外:如果grad_array讲究座次是天经地义的,那么grad_dict讲究这些是为什么呢?字典dict本来就是靠名字索引的嘛。没办法,在mxnet的地盘上,得听话。只是这才两个变量,对于LSTM的40多个变量该怎么整呢?选择需要的9个变量求其梯度??两个都不行,9个能行吗?必定不行,尽管当时也是试了的。在执行backward之前,还反复print了grad_dict和grad_array的内容,发现其变量的排位非常奇怪,也说不出有什么规律。总之,不管规律在还是不在,41个梯度变量都在那。
睡了一觉,醒来终于想出点眉目了:用grad_array做backward()的输入,然后通过grad_dict选出需要的Ux、Wx、V这9个矩阵变量的梯度。什么意思呢?就是用grad_array数组做backward的输入,这样自然符合梯度排位的要求,因为这是backward()之前mxnet自己的安排。执行完backward()之后,用字典grad_dict取出所需的变量的梯度,因为你并不知道grad_aray[0]…grad_dict[xxx]是哪个变量的梯度。果然,backwrad()不再报错,print梯度值,也是有的。

out_grads = []
for i in range(len(executor.grad_arrays)):
      out_grads.append(mx.nd.ones(executor.grad_arrays[i].shape))

 executor.backward(out_grads)

但是,但是,但是,mxnet版的LSTM最终还是没有正确的训练过程。
不过,此时距正确答案只有最后一步了。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有