PyTorch爬坑指南

用PyTorch也有個把年頭了,可是偶爾仍是會出現各類各樣的bug,當bug積累到必定數量就會引發質變——寫成一個筆記來長記性……bash

Variable和Tensor

0.4以後的版本能夠直接用Tensor代替Variable了,不須要再繁瑣得包裹一層Variable網絡

Tensor的轉置

tensor的轉置有不少方式,好比.transpose.transpose_是最經常使用的(帶下劃線的是在原tensor上作改變),可是這個轉置方式有個限制就是僅能對兩個維度進行操做,是個「根正苗紅」的轉置函數。app

有時候須要將更多的維度進行交換,這個時候就能夠用.permuate函數,該函數接受一個維度列表,而後根據列表中的維度的排列順序更新tensor的維度排列。機器學習

升維和降維

升維.unsqueeze是在指定的維度上插入一維,可是數據實際上沒有發生變化。好比下面得示例:函數

In [1]: a
Out[1]:
tensor([[[-0.7908, -1.4576, -0.3251],
         [-1.2053,  0.3667,  0.9423],
         [ 0.0517,  0.6051, -0.1360],
         [ 0.8666, -1.4679, -0.4511]]])

In [2]: a.shape
Out[2]: torch.Size([1, 4, 3])

In [3]: a.unsqueeze(2).shape
Out[3]: torch.Size([1, 4, 1, 3])
複製代碼

降維.squeeze是升維得逆操做,會消除全部維度爲1的維度:學習

In [1]: a.shape
Out[1]: torch.Size([1, 4, 3])

In [2]: a.squeeze().shape
Out[2]: torch.Size([4, 3])
複製代碼

Tensor的contiguous

一個Tensor執行轉置操做後,實際上數據並無發生任何變化,只是讀取數據的順序變化了。這是一種節約空間和運算的方法。ui

不過轉置以後的數據若是須要進行.view等操做的話,因爲數據在邏輯上並不連續,所以須要手動調用contiguous讓數據恢復連續存儲的模樣。spa

Loss Functions的參數格式究竟是啥啊

這個是早期常常會遇到的狀況,咋一會要FloatTensor,一會就要LongTensor了?要解決這個問題,仍是須要深刻了解不一樣的loss function到底在作啥。調試

BCELoss

二分類的交叉熵,計算公式以下:code

\sum_{i}{-y_ilog\hat{y_i}-(1-y_i)log(1-\hat{y_i})}

這樣就不難看出 y_i\hat{y_i} 都須要是FloatTensor了。若是是不一樣的tensor進行運算會出錯:

Expected object of type torch.FloatTensor but found type torch.LongTensor
複製代碼

CrossEntropyLoss

上面的BCELoss是計算二分類狀況下的損失函數,那麼當遇到多分類問題的時候,也想用交叉熵來計算的話,就須要用到CrossEntropyLoss了。

不管是二分類仍是多分類,均可以用最簡單的L2損失來計算,可是若是使用Sigmoid做爲激活函數的話容易致使梯度消失,致使效果很差

自定義網絡必定要初始化權值

權值不初始化會出事的……

有時候會遇到一些結構清奇的網絡,各類參數糾纏在一塊兒,所以torch自帶的函數可能不夠用,那就須要自定義網絡結構了。通常會從最底層的矩陣開始構建,就像tensorflow同樣。好比定義一個不帶偏置項的全鏈接層能夠用nn.Linear(xxx, xxx, bias=False)。但特殊狀況下甚至連全鏈接層內部的細節都要本身定義,這個時候就須要參考torch的源碼了,好比全鏈接層的部分源碼:

class Linear(Module):
    def __init__(self, in_features, out_features, bias=True):
        super(Linear, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.Tensor(out_features, in_features))
        if bias:
            self.bias = Parameter(torch.Tensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def forward(self, input):
        return F.linear(input, self.weight, self.bias)
複製代碼

在這個例子中已經能學到不少了,這裏重點參考Parameters的使用(out_feature在前)和權值初始化函數reset_parameters的內部細節。

以前一次機器學習的做業,相同的模型,PyTorch版始終達不到Tensorflow版的準確率,大概就是權值初始化二者存在差別吧。

CUDA的一些坑

自定義的網絡不會自動轉cuda??

以前有用過本身先定義了一個簡單的底層網絡,方便上層網絡的構建。這樣定義出來的網絡在CPU上運行的好好的,可是當我把模型遷移到GPU上時卻發現模型出現了類型不符的錯誤:

Expected object of type torch.FloatTensor but found type torch.cuda.FloatTensor for argument
複製代碼

後來在調試的時候發現對上層網絡調用.cuda後,下層網絡卻仍是處於device(type='cpu')!雖然解決這個問題最簡單的方法就是把底層網絡的定義放到上層網絡中,可是這樣不利於代碼的維護,而且在不一樣網絡中共享模塊也會變得很複雜。

PyTorch在CPU和GPU數據上的遷移的確沒有tensorflow來的方便……

在GitHub上看到不少相似的問題的解決方法,其中有一個點子很不錯:

if torch.cuda.available():
    import torch.cuda as t
else:
    import torch as t
複製代碼

若是一開始寫代碼的時候就考慮到自動CUDA的話,這的確是個好辦法,若是已經寫了臃腫的代碼再改寫仍是會很繁瑣。

最終我選擇的方案仍是重寫.cuda函數:

def cuda(self, device=None):
    r"""Moves all model parameters and buffers to the GPU..."""
    return self._apply(lambda t: t.cuda(device))
複製代碼

.cuda函數中對子模塊也調用.cuda就能夠了。

CUDA數據不能直接轉numpy數據

若是一個數據已經遷移到GPU上了,那麼若是要將運算結果轉爲符合numpy特性的數據就不是這麼直接的了,通常會報錯:

TypeError: can't convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first. 複製代碼

解決方案報錯信息已經給了。固然還能夠將以後須要numpy來幫忙的運算改爲PyTorch支持的運算,這樣速度還更快呢,畢竟條條大路通羅馬。

相關文章
相關標籤/搜索