1. 程式人生 > >pytorch筆記(四)

pytorch筆記(四)

1.Variable是神經網路中特有的一個概念,提供了自動求導的功能,在numpy中沒有該功能,存在與torch.autograd.Variable().
  通過data可以取出Variable中的tensor數值,grad_fn表示的是得到這個Variable的操作,比如是通過加減還是乘除得來的,最後grad是這個Variable的反向傳播梯度。
```
對於標量的求導
x = Variable(torch.Tensor([1]),requires_grad = True)
w = Variable(torch.Tensor([2]),requires_grad = True)
b = Variable(torch.Tensor([3]),requires_grad = True)

Y = w*x+b

Y.backward()   #即所謂的自動求導,不需要指定哪個函式對哪個函式求導,
               # 直接通過這行程式碼就能對所有的需要梯度的變數進行求導,然後可以直接通過  變數.grad得到需要的梯度

print(x.grad)   #得到tensor([2.])
print(x.data)   #得到tensor([1.])
```

```
對於矩陣的求導
x = torch.randn(3)
x = Variable(x,requires_grad = True)
y = x*2
#y.backward()  #y為向量,不能使用該語句,必須傳入相應的引數
y.backward(torch.FloatTensor([1,0.1,0.01]))   #代表得到的梯度分別乘1,0.1,0.01
print(x)    #輸出:tensor([ 1.0583,  2.0384,  0.4226])
print(x.grad)   #輸出: tensor([ 2.0000,  0.2000,  0.0200])
```
2.優化過程中各個步驟的解釋
```
criterion = nn.CrossEntropyLoss()
loss = criterion(output,target)
optimizer = torch.optim.SGD(mynet.parameters(),lr = 0.01,momentum = 0.9)
optimizer.zero_grad()       #梯度清零
loss.backward()             #求得每個引數的梯度
optimizer.step()            #引數更新
```
3.模型的儲存與載入
model為模型的名字
模型的儲存和載入有兩種方式:
(1) 僅僅儲存和載入模型引數:相對來說較為靈活
torch.save(the_model.state_dict(), PATH)
the_model.load_state_dict(torch.load(PATH))
(2) 儲存和載入整個模型
torch.save(the_model, PATH)
the_model = torch.load(PATH)

4.每次做反向傳播之前都要歸零梯度,不然梯度會累積在一起,造成結果不收斂,要注意:loss是一個Variable,所以要通過loss.data取出其中的Tensor,再通過loss.data[0]得到一個int或者float型別的資料,這樣我們才能打印出相應的資料。

5.Variable volatile=True  代表不計算梯度,預設為False,通過該變數產生的子變數的volatile數值相同

6.detach 代表隔斷梯度的傳播??  比如:
```
# y=A(x), z=B(y) 求B中引數的梯度,不求A中引數的梯度
# 第一種方法
y = A(x)
z = B(y.detach())
z.backward()

# 第二種方法
y = A(x)
y.detach_()
z = B(y)
z.backward()
```

7.反向傳播計算梯度的時候,self.loss.backward(retain_graph=retain_graph)的作用:
  ```
class ContentLoss(nn.Module):
    def __init__(self, target, weight):
        super(ContentLoss, self).__init__()
        self.target = target.detach() * weight
        # 因為這裡只是需要target這個數值,這個數值是一種狀態,不計入計算樹中。
        # 這裡單純將其當做常量對待,因此用了detach則在backward中計算梯度時不對target之前所在的計算圖存在任何影響。
        self.weight = weight
        self.criterion = nn.MSELoss()
    def forward(self, input):
        self.loss = self.criterion(input * self.weight, self.target)
        self.output = input
        return self.output
    def backward(self, retain_graph=True):
        self.loss.backward(retain_graph=retain_graph)
        return self.loss
  ```
看到上面的程式碼,我們在內容損失層中定義了一個backward()反向反饋函式。這個函式在整個神經網路在反向迴圈的時候會執行loss的backward從而實現對loss的更新。

但是在這個程式碼中,我們設定了retain_graph=True,這個引數的作用是什麼,官方定義為:

retain_graph (bool, optional) – If False, the graph used to compute the grad will be freed. Note that in nearly all cases setting this option to True is not needed and often can be worked around in a much more efficient way. Defaults to the value of create_graph.

大意是如果設定為False,計算圖中的中間變數在計算完後就會被釋放。但是在平時的使用中這個引數預設都為False從而提高效率,和creat_graph的值一樣。
其實retain_graph這個引數在平常中我們是用不到的,但是在特殊的情況下我們會用到它:

假設一個我們有一個輸入x,y = x **2, z = y*4,然後我們有兩個輸出,一個output_1 = z.mean(),另一個output_2 = z.sum()。然後我們對兩個output執行backward。
```
In[3]: import torch
In[5]: x = torch.randn((1,4),dtype=torch.float32,requires_grad=True)
In[6]: y = x ** 2
In[7]: z = y * 4
In[8]: output1 = z.mean()
In[9]: output2 = z.sum()
In[10]: output1.backward()    # 這個程式碼執行正常,但是執行完中間變數都free了,所以下一個出現了問題
In[11]: output2.backward()    # 這時會引發錯誤
Traceback (most recent call last):
  File "/home/prototype/anaconda3/envs/pytorch-env/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-11-32d5139229de>", line 1, in <module>
    output2.backward()
  File "/home/prototype/anaconda3/envs/pytorch-env/lib/python3.6/site-packages/torch/tensor.py", line 93, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph)
  File "/home/prototype/anaconda3/envs/pytorch-env/lib/python3.6/site-packages/torch/autograd/__init__.py", line 89, in backward
    allow_unreachable=True)  # allow_unreachable flag
RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.
```
如果我們這樣寫:
```
In[3]: import torch
  ...: from torch.autograd import Variable
  ...: x = torch.randn((1,4),dtype=torch.float32,requires_grad=True)
  ...: y = x ** 2
  ...: z = y * 4
  ...: output1 = z.mean()
  ...: output2 = z.sum()
  ...: output1.backward(retain_graph=True)   # 這裡引數表明保留backward後的中間引數。
  ...: output2.backward()
```
有兩個輸出的時候就需要用到這個引數,這就和之前提到的風格遷移中Content Loss層為什麼使用這個引數有了聯絡,因為在風格遷移中不只有Content Loss層還有Style Loss層,兩個層都公用一個神經網路的引數但是有兩個loss的輸出,因此需要retain_graph引數為True去保留中間引數從而兩個loss的backward()不會相互影響。
```
# 假如你有兩個Loss,先執行第一個的backward,再執行第二個backward
loss1.backward(retain_graph=True)
loss2.backward() # 執行完這個後,所有中間變數都會被釋放,以便下一次的迴圈
optimizer.step() # 更新引數
```
這樣就比較容易理解了。retain_variables=True,這個引數預設是False,也就是反向傳播之後這個計算圖的記憶體會被釋放,這樣就沒辦法進行第二次反向傳播了,所以我們需要設定為True,因為這裡我們需要進行兩次反向傳播

8.風格遷移,在計算Gram矩陣的時候,第一種理解方法為厚度,第二種理解方法為與轉置矩陣的乘積。
  第一種方法:對於風格圖,在某個卷積層中得到一個C X M X N的特徵圖,先取出第 i 層和第 j 層 的引數,對應相乘,然後累加,得到Gram矩陣中座標為i,j的點,如1*1,1*2,1*3.......,在理解的時候,可把每一層拉伸為1維向量(1,M * N),矩陣size 為 C X M*N ,與轉置相乘,就能得到C X C 的Gram風格矩陣。也對應於第二種方法。
![image.png](https://upload-images.jianshu.io/upload_images/8035477-81ccf588583ed125.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)