轉自:Pytorch中文網
接觸了PyTorch這麼長的時間,也玩了很多PyTorch的騷操作,都特别簡單直覺地實作了,但是有一個網絡訓練過程中的操作之前一直沒有仔細去考慮過,那就是
loss.backward()
,看到這個大家一定都很熟悉,loss是網絡的損失函數,是一個标量,你可能會說這不就是反向傳播嗎,有什麼好講的。
但是不知道大家思考過沒有,如果loss不是一個标量,而是一個向量,那麼
loss.backward()
是什麼結果呢?
大家可以去試試,寫一個簡單的小程式
1 2 3 4 5 | import torch as t from torch.autograd import Variable as v x = v(t.ones(2, 2), requires_grad=True) y = x 1 y.backward() |
運作一下程式,恭喜你報錯了,錯誤顯示如下
我們來讀一讀這個錯誤是什麼意思。backward隻能被應用在一個标量上,也就是一個一維tensor,或者傳入跟變量相關的梯度。
嗯,前面一句話很簡單,backward應用在一個标量,平時我們也是這麼使用的,但是後面一句話,with gradient w.r.t variable是什麼鬼,傳入一個變量相關的梯度。不了解啊不了解,看不懂沒關系我們還可以做實驗來解決這個問題,俗話說自己動手豐衣足食(我也想做個伸手黨去看看别人寫的,然後不幸地是并沒有什麼人寫過這方面的東西)。
首先我們開始做一個簡單的實驗,就是複習一下标量的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # simple gradient a = v(t.FloatTensor([2, 3]), requires_grad=True) b = a 3 c = b * b * 3 out = c.mean() out.backward() print(\'*\'*10) print(\'=====simple gradient======\') print(\'input\') print(a.data) print(\'compute result is\') print(out.data[0]) print(\'input gradients are\') print(a.grad.data) |
很簡單,我們把數學表達式寫出來,傳入的參數x1=2,x2=3
x1=2,x2=3
,特别注意
Variable
裡面預設的參數
requires_grad=False
,是以這裡我們要重新傳入
requires_grad=True
讓它成為一個葉子節點。
那麼我們對其求偏導也很簡單(分别為15,18) 這樣依靠簡單的微積分知識我們就能夠算出他們的結果,運作一下程式,確定結果一緻,ok。
下面我們研究一下如何能夠對非标量的情況下使用backward,下面開始做實驗(瞎試)。
1 2 3 4 | m = v(t.FloatTensor([[2, 3]]), requires_grad=True) n = v(t.zeros(1, 2)) n[0, 0] = m[0, 0] ** 2 n[0, 1] = m[0, 1] ** 3 |
第一想法就是裡面這個參數是要求梯度的對象,我們這樣調用
n.backward(m.data)
,有有報錯诶,是不是成功了,我真的是個天才,這麼難的東西都能想到,等等,我好想看到了一個很神奇的結果。
這是什麼鬼,這跟說好的結果不一樣啊,我們想要的結果是4和27,現在給我們的結果是8和81,為什麼會出現這樣神奇的結果呢,想不通啊。我們看看我們傳入的參數是
m.data
,這是一個(2, 3)的向量,我們希望得到的梯度是(4, 27),好像(4×2=8, 27×3=81),我的内心毫無波動,甚至有點想笑,似乎backward将我傳入的參數
m.data
乘上了得到的梯度,既然要乘上我傳入的參數,那麼我就給你傳入1,這樣總能得到我想要的結果了吧,
n.backward(t.FloatTensor([[1, 1]]))
,看看結果呢
哇,跟我們想要的結果一樣诶,撒花,我們解決了一個大問題,就是這麼簡單,扔進去一個1就可以了,這個問題也沒有那麼難嘛,哈哈哈。
似乎又有一點不對,如果這麼簡單那麼寫PyTorch的人為什麼不把這一步直接內建進去,那我們不就不會遇到這個問題了嘛。
我們來試試另外一種情況
1 2 3 4 5 6 | m = v(t.FloatTensor([[2, 3]]), requires_grad=True) j = t.zeros(2 ,2) k = v(t.zeros(1, 2)) m.grad.data.zero_() k[0, 0] = m[0, 0] ** 2 + 3 * m[0 ,1] k[0, 1] = m[0, 1] ** 2 + 2 * m[0, 0] |
麼我們直接對k反向傳播
k.backward(t.FloatTensor([[1, 1]])
,結果是什麼呢?
首先我們手動算一算結果是什麼:4,3,2,6,我們是希望能夠得到上面四個結果,這個時候你可能已經開始懷疑了,能夠得到這4個結果嗎?我們可以輸出結果來看看
非常遺憾,我們隻得到了兩個結果,并且數值并不對,這個時候你就會疑惑了,到底是哪裡出了問題呢,為什麼會得到這樣的結果呢?
經過不斷地嘗試,我終于發現了其中的奧秘,
k.backward(parameters)
接受的參數
parameters
必須要和
k
的大小一模一樣,然後作為
k
的系數傳回去,什麼意思呢,我們通過上面的例子來解釋這個問題你就知道了。
我們已經知道我們得到的k=(k1,k2)
k=(k1,k2)
,以及傳入的參數是1和1,那麼是如何得到這6和9這兩個結果的呢?
我們知道了這個操作具體是怎麼完成的,我們就可以求求我們需要的這個jacobian矩陣了,非常簡單。
1 2 3 4 5 6 7 8 9 10 11 12 13 | # jacobian j = t.zeros(2 ,2) k = v(t.zeros(1, 2)) m.grad.data.zero_() k[0, 0] = m[0, 0] ** 2 + 3 * m[0 ,1] k[0, 1] = m[0, 1] ** 2 + 2 * m[0, 0] k.backward(t.FloatTensor([[1, 0]]), retain_variables=True) j[:, 0] = m.grad.data m.grad.data.zero_() k.backward(t.FloatTensor([[0, 1]])) j[:, 1] = m.grad.data print('jacobian matrix is') print(j) |
我們可以得到如下結果
這裡我們要注意
backward()
裡面另外的一個參數
retain_variables=True
,這個參數預設是False,也就是反向傳播之後這個計算圖的記憶體會被釋放,這樣就沒辦法進行第二次反向傳播了,是以我們需要設定為True,因為這裡我們需要進行兩次反向傳播求得jacobian矩陣。
最後我們再舉一個矩陣乘法的例子試驗一下我們的結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | x = t.FloatTensor([2, 1]).view(1, 2) x = v(x, requires_grad=True) y = v(t.FloatTensor([[1, 2], [3, 4]])) z = t.mm(x, y) jacobian = t.zeros((2, 2)) z.backward(t.FloatTensor([[1, 0]]), retain_variables=True) # dz1/dx1, dz2/dx1 jacobian[:, 0] = x.grad.data x.grad.data.zero_() z.backward(t.FloatTensor([[0, 1]])) # dz1/dx2, dz2/dx2 jacobian[:, 1] = x.grad.data print('=========jacobian========') print('x') print(x.data) print('y') print(y.data) print('compute result') print(z.data) print('jacobian matrix is') print(jacobian) |
上面是代碼,仔細閱讀,作為一個小練習回顧一下本篇文章講的内容,媽媽再也不用擔心我不會用
backward
了。