2020年4月25日 星期六
PyTorch no_grad
在進行 back-propagation 時,有些路徑是我們不想要去計算 gradient 的,這時我們可以用 no_grad() 這個 function。但有一個需要注意的地方是,只有在 no_grad() 底下產生出來的 tensor 才會被 disable gradient。以下舉兩個例子:
a = torch.tensor(1.0, requires_grad=True)
with torch.no_grad():
b = a
b.backward()
這時候我們去看 a.grad,會發現還是有 gradient 產生。原因就在於,這個 b = a 其實只是讓 b 成為一個 a 的 reference,也就是 b 其實就是 a,只是名字換了而已。在這種情況下,b 不是一個在 no_grad() 底下產生的 tensor,因此還是會有 gradient。
a = torch.tensor(1.0, requires_grad=True)
with torch.no_grad():
b = a + 0
b.backward()
假如改成 b = a + 0,那所產生的 b 就是一個新的 tensor,並且此 tensor 是不會被計算 gradient 的。
2020年4月13日 星期一
PyTorch 如何追蹤 backward 路徑
next_functions
當我們在建立一個 model 並進行訓練時,有時候可能 forward 結果是對的,但是訓練結果卻不如預期,這時候可能會想要知道 back propagation 的運算是否正確。這時候應該怎麼做呢?我們舉以下這段程式為例:a = torch.tensor(1.1, requires_grad=True)
b = torch.tensor(1.2, requires_grad=True)
c = torch.tensor(1.3, requires_grad=True)
d = a * b
e = d * c
e.backward()
我們可以使用 e.grad_fn.next_functions 來追蹤 backward 的路徑。e.grad_fn.next_functions 是一個 list,其中包含了 e 往前走一層的所有 gradient function。上面這個例子中,e.grad_fn.next_functions 中包含兩個 element,一個是往 d 那邊走的 gradient function,另一個是往 c 那邊走的。
往 d 走的是 e.grad_fn.next_functions[0][0]
往 c 走的是 e.grad_fn.next_functions[1][0]
若我們想要知道 c 這條路徑的 forward 運算結果和 gradient,分別是:
Forward 結果:e.grad_fn.next_functions[1][0].variable (結果為 1.3)
Gradient 大小:e.grad_fn.next_functions[1][0].variable.grad (結果為 1.32)
而由於 d 這條路徑還有上游,我們必須再往上一層才能抵達 a 和 b
其中往 a 這條路徑的 forward 運算結果和 gradient,分別是:
Forward 結果:e.grad_fn.next_functions[0][0].next_functions[0][0].variable (結果為 1.1)
Gradient 大小:e.grad_fn.next_functions[0][0].next_functions[0][0].variable.grad (結果為 1.56 = 1.3 * 1.2)
而往 b 這條路徑的 forward 運算結果和 gradient,分別是:
Forward 結果:e.grad_fn.next_functions[0][0].next_functions[1][0].variable (結果為 1.2)
Gradient 大小:e.grad_fn.next_functions[0][0].next_functions[1][0].variable.grad (結果為 1.43 = 1.3 * 1.1)
只有位於 leaf node 的 grad_fn 有 variable 這個 attribute,因此必須使用 next_function 不斷的往前追蹤到所有的 leaf node 位置,過程中我們可以用 hasattr(object, name) 來確認 grad_fn 是否包含 variable 這個 attribute。
register_hook
如上所述,只有 graph 的 leaf node 存在 variable 能夠用於追蹤 gradient back-propagation。假如我們想要知道 internal node d 的 gradient 時,可以使用 register_hook。承接上面這個例子,我們可以使用如下程式碼來取得 d 的 gradient。
grads = {}
def save_grad(name):
def hook(grad):
grads[name] = grad
return hook
d.register_hook(save_grad('d'))
在做完 e.backward() 以後,就能夠用 grads['d'] 來存取 d 路徑上的 gradient,其結果為 1.3。
Reference
[1] https://stackoverflow.com/questions/52988876/how-can-i-visualize-what-happens-during-loss-backward
[2] https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/368709/
[3] https://discuss.pytorch.org/t/why-cant-i-see-grad-of-an-intermediate-variable/94/6
2020年4月7日 星期二
PyTorch register_buffer
通常我們想要建立一個 tensor 時,會用以下方法:
a = torch.tensor(0.1)
假如要在一個 module 中建立一些 tensor,可能會使用如下方法:
class Layer(nn.Module):
self.__init__(self, num_outputs, num_inputs):
self.w = nn.Parameter(torch.Tensor(num_outputs, num_inputs))
self.a = torch.tensor(0.1)
其中的 w 是可以被訓練的參數,而 a 是無法被訓練的。
這時我們會遇到一個問題,當我們想要將整個 module 從 CPU 移動到 GPU 時:
layer = Layer(10, 100)
layer = layer.to('cuda:0')
我們會發現 w 可以成功的移動,但是 a 卻仍然停留在 CPU 上。其中的差異就在,nn.Parameter 會將 w 宣告成一個 module 內的參數,但是用一般 torch.tensor 方法宣告的卻不會。
那麼假如我們想要在 module 內建立一個不可訓練的參數要怎麼做呢?答案是:使用 register_buffer 功能。
以上面的例子來說,a 可以改用以下方式宣告:
self.__init__(self):
self.register_buffer('a', torch.tensor(0.1))
之後我們要使用 a 時,一樣用 self.a 就可以使用了。如此一來,當我們將 module 移動到 GPU 時,a 也會跟著一起移動。
2020年3月30日 星期一
PyTorch 特殊函數之求導
torch.max() / torch.min()
對 torch.max() 進行 backward() 時,對輸入的 tensor 中數值最大的元素的 gradient 會貢獻 1,其餘則為 0。對 torch.min() 進行 backward() 時,對數值最小的元素的 gradient 會貢獻 1,其餘則為 0。
程式
a = torch.tensor([1., 2., 3.], requires_grad=True)
b = torch.max(a)
b.backward()
a.grad
輸出
tensor([0., 0., 1.])
torch.abs()
對 torch.abs() 進行求導時,對輸入的 tensor 中正的元素的 gradient 貢獻為 1,對負的元素的 gradient 貢獻為 -1,對元素值為 0 的 gradient 貢獻為 0。程式
a = torch.tensor([-1., -2., 3., 0.], requires_grad=True)
b = torch.abs(a)
b.backward(torch.ones_like(b))
a.grad
輸出
tensor([-1., -1., 1., 0.])
torch.where()
d = torch.where(a, b, c)a 通常是一個 bool type 的張量,在 a 張量裡面是 True 的位置,b 該位置的元素值會傳到 d 的相應位置,在 a 張量裡面是 False 的位置,c 該位置的元素值會傳到 d 的相應位置。當我們對 where 進行 backward 時,在 b 和 c 當中有傳到 d 的那些位置的 grad 是 1,其餘位置的 grad 是 0。
程式
a = torch.tensor([True, False, False, True, True])
b = torch.tensor([1., 2., 3., 4., 5.], requires_grad=True)
c = torch.tensor([2., 4., 6., 8., 10.], requires_grad=True)
d = torch.where(a, b, c)
d
d.backward(torch.ones_like(d))
b.grad
c.grad
輸出
tensor([1., 4., 6., 4., 5.], grad_fn=<SWhereBackward>) # d
tensor([1., 0., 0., 1., 1.]) # b.grad
tensor([0., 1., 1., 0., 0.]) # c.grad
求導結果為 0 的函數
sign, ceil不可求導的函數
argmax, argmin, lt, le, eq, ne, ge, gt當一條運算路徑中有經過不可求導函數時,在進行 backward() 時,這條路徑就無法進行求導運算了。
PyTorch add function
在 PyTorch 中的 add function,可以實現將兩個張量相加,可以寫成 c = torch.add(a, b),也可以直接寫成 c = a + b。
其中 a 和 b 分別可以是一個張量加上一個純量,結果 c 就會是把純量加到張量中的每一個元素上。
a 和 b 也可以是兩個張量,但是若要實現兩個張量的相加,a 和 b 的 size 就有特定的限制 (broadcastable)。
以下幾種 a 和 b 的 size 是允許相加的:
Size of a: (5)
Size of b: (3, 2, 1, 5)
Size of a: (1, 5)
Size of b: (3, 2, 1, 5)
Size of a: (2, 1, 5)
Size of b: (3, 2, 1, 5)
Size of a: (3, 2, 1, 5)
Size of b: (3, 2, 1, 5)
由以上的例子可以看出,其中一個張量 (a) 的維度必須小於或等於另一個張量 (b),而且 a 的 size 必須與另一個張量 b 的最後幾個維度的 size 相同。
以下幾種 a 和 b 也是允許相加的:
Size of a: (3, 2, 2, 5)
Size of b: (1, 5)
Size of a: (3, 2, 2, 5)
Size of b: (1, 1, 5)
Size of a: (3, 2, 2, 5)
Size of b: (2, 1, 5)
Size of a: (3, 2, 2, 5)
Size of b: (3, 1, 1, 5)
Size of a: (1, 5)
Size of b: (2, 1)
Size of a: (1, 1, 1, 5)
Size of b: (1, 2, 1, 1)
由以上的例子可以看出,在其中一個張量的維度中,可以允許一個或多個維度的大小為 1。在大小為 1 的那個維度中,b (a) 的所有元素會被廣播到 a (b) 裡面同一個維度中所有的 index 上並執行加法運算。
2020年3月26日 星期四
PyTorch 中 weight 和 weight.data 的差異
當我們做完 backpropagation 得到 weight 和 bias 的 grad 之後,要進行 weight 和 bias 的更新時,有兩種不同的 coding style。
(1) 使用 torch.no_grad()
with torch.no_grad():
weight -= learning_rate * weight.grad
bias -= learning_rate * bias.grad
(2) 使用 weight.data 和 weight.grad.data
weight.data -= learning_rate * weight.grad.data
bias.data -= learning_rate * bias.grad.data
這兩種方法有何區別呢?根據 PyTorch 官方網站給的解釋:
tensor 和 tensor.data 共享同一個儲存空間,但是當我們使用 tensor.data 時,PyTorch 不會追蹤運算的歷史,也就是當我們進行 backward() 時,在 tensor.data 中不會進行 gradient 的計算。Recall that tensor.data gives a tensor that shares the storage with tensor, but doesn't track history.
Reference
https://pytorch.org/tutorials/beginner/examples_autograd/two_layer_net_autograd.html
PyTorch 的 nn 和 nn.functional 的差別
在 nn 裡面實現了很多搭建神經網路時常用的 building blocks 如 Conv2D、ReLu 等等。但是相同的名稱在 nn.functional 裡面也能找到,那它們之間有何差別?
差別在於 nn 裡面的 building blocks 是用 Python 的 Class 來實現的,他繼承了nn.Module 這個 Class,像 weight 和 bias 等 variable 會存在 nn.Conv2D 裡面。但是對於 nn.functional.Conv2D 來說,因為它只是一個 function 而不是 class,function 本身是沒有狀態的 (state),所有一切 variable 在 function 結束以後就消失了,因此它的 weight 和 bias 等需要保留的 variable 就必須從外面傳入。
這也是為什麼,當我們創建一個 network 時,nn.Conv2D 要寫在 self.__init__ 裡面,但是 nn.functional.relu 卻不用。
Reference
https://discuss.pytorch.org/t/what-is-the-difference-between-torch-nn-and-torch-nn-functional/33597
訂閱:
文章 (Atom)
