天天看點

Pytorch1.1.0 入門 自定義op(python)用python進行pytorch擴充(繼承autograd.Function)用C++進行pytorch擴充(繼承autograd.Function)用C進行pytorch擴充(繼承autograd.Function)Python 積累

因為需求,需要調研tensorRT與ONNX關于自定義層的方法。經過之前的調研,首先,關于onnx,開發者手冊中的介紹有限,在已知的demo中沒有關于onnx自定義層的,詳情見TensorRT 5.1.5.0入門 Pytorch & ONNX.

後來,自己下載下傳了onnx-tensorrt的代碼onnx-tensorrt,發現NvOnnxParser.h中隻寫了IParser類,不像NvCaffeParser.h包裝了IPluginFactory和IPluginFactoryExt等類,是以相當于是沒有自定義層接口的。

然後查詢資料發現,pytorch基于op,是以學習一下pytorch擴充op的方法。

文章結構

  • 用python進行pytorch擴充(繼承autograd.Function)
    • 建立op操作(函數)(擴充torch.autograd)
    • 使用自定義的op(擴充 torch.nn)建立自定義layer
      • nn.Parameter
      • .data.uniform_(-0.1,0.1)
  • 用C++進行pytorch擴充(繼承autograd.Function)
  • 用C進行pytorch擴充(繼承autograd.Function)
  • Python 積累
    • 1. super()
    • 2. isinstance()

去pytorch1.1.0的官網看,有extending pytorch的子產品 extending_pytorch,有三種擴充方法,python,c++和C。中文版本 擴充pytorch.

用python進行pytorch擴充(繼承autograd.Function)

添加op,需要用autograd的Function為每個操作實作一個新的子類。其實,Function就是autograd用來計算結果和漸變以及編碼操作曆史的子產品。

建立op操作(函數)(擴充torch.autograd)

同時參考了之前版本pytorch自定義層實作博文Pytorch入門學習(八)-----自定義層的實作(甚至不可導operation的backward寫法),根據步驟定義了自己的LinearFunction.

from torch.autograd import Function
	@staticmethod
    def forward(ctx,input,weight,bias=None,beta_f=1.0,alpha_f=1.0):
        ctx.save_for_backward(input,weight,bias)
        ctx.beta=beta_f
        ctx.alpha=alpha_f

        output=input.mm(weight.t())
        if bias is not None:
            output+=bias.unsqueeze(0).expand_as(output)
        return output

    @staticmethod
    def backward(ctx,grad_output):
        input,weight,bias=ctx.saved_variables
        grad_input=grad_weight=grad_bias=None

        if ctx.needs_input_grad[0]:
            grad_input=grad_output.mm(weight)
        if ctx.needs_input_grad[1]:
            grad_weight=grad_output.t().mm(input)
        if bias is not None and ctx.needs_input_grad[2]:
            grad_bias=grad_output.sum(0).squeeze(0)
        return grad_input,grad_weight,grad_bias,None,None

           

總的來說,我的了解就是繼承torch.autograd.Function基類,然後寫forward和backward操作,symbolic是後面轉onnx相關的,和自定義op暫時無關。

在1.1.0版本的pytorch,variable和tensor已經統一了,是以好像不用考慮很多部落格提到的轉tensor問題。

forward:

(1)首先用save_for_backward是用來存tensor的,存起來之後留給backward的時候用。

(2)下面就是全連接配接層的正常操作。

backward:

(1)首先,從ctx中取出需要的tensor(以前的功能也有轉成variable,現在可能沒有了吧,我太知道)

(2)grad_output,是否梯度改變,即requires_grad是否為True取決于在外面調用.backward或是.grad時候的那個Variable是不是需要grad的。如果那個Variable是需要grad的,那麼我們這裡反向的grad_ouput也是requires_grad為True,那麼我們甚至可以計算二階梯度。用WGAN-GP之類的。

使用自定義的op(擴充 torch.nn)建立自定義layer

官網上說(劃重點):

nn 包含兩種接口 - modules和他們的functional版本。通過這兩個接口,你都可以擴充nn。但是我們建議,在擴充layer的時候,使用modules, 因為modules儲存着參數和buffer。如果不需要參數的話,那麼建議使用functional(激活函數,pooling等)。

增加一個operation的 functional版本已經在上面一節介紹完畢。

是以就用Module了,都是官網上有的代碼:

def __init__(self,input_featrues,output_features,bias=True,beta=1.0,alpha=1.0):
        super(Linear,self).__init__()
        self.input_features=input_featrues
        self.output_features=output_features

        self.weight=nn.Parameter(torch.Tensor(output_features,input_featrues))
        if bias:
            self.bias=nn.Parameter(torch.Tensor(output_features))
        else:
            self.register_parameter('bias',None)
        self.weight.data.uniform_(-0.1,0.1)
        if bias is not None:
            self.bias.data.uniform_(-0.1,0.1)
        # 這裡的beta和alpha沒有實際用處,隻是證明使用自定義的op,在torch->onnx過程中,是可以傳遞網絡參數的。
        self.beta=beta
        self.alpha=alpha
    def forward(self,input):
        return LinearFunction.apply(input,self.weight,self.bias,self.beta,self.alpha)
           

這裡,除了告訴我,要用.apply來引用自定義op之外。

作為pytorch小白的我,學到的就是nn.Parameter,uniform_

nn.Parameter

torch.nn.Parameterm,在看過很多部落格的時候發現了一個用法self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size)),首先可以把這個函數了解為類型轉換函數,将一個不可訓練的類型Tensor轉換成可以訓練的類型parameter并将這個parameter綁定到這個module裡面(net.parameter()中就有這個綁定的parameter,是以在參數優化的時候可以進行優化的),是以經過類型轉換這個self.v變成了模型的一部分,成為了模型中根據訓練可以改動的參數了。使用這個函數的目的也是想讓某些變量在學習的過程中不斷的修改其值以達到最優化。

換句話說就是個可訓練的tensor

主要用于達到學習的效果,如下例所示,concat注意力機制

class Attn(torch.nn.Module):
	def __init__(self,method,hidden_size):
		super(Attn,self).__init__()
		self.method=method
		if self.method not in ['dot','general','concat']:
			raise ValueError(self.method, "is not an supportted method")
		self.hidden_size=hidden_size
		if self.method == 'general':
			self.attn=torch.nn.Linear(self.hidden_size,hidden_size)
		elif self.method=='concat':
			self.attn=torch.nn.Linear(self.hidden_size*2,hidden_size)
			self.v=torch.nn.Parameter(torch.FloatTensor(hidden_size))
	...
           

self.v需要不斷學習,而實驗發現,linear裡面的weight和bias就是parameter類型,且不能夠使用tensor類型替換,還有linear裡面的weight甚至可能通過指定一個不同于初始化時候的形狀進行模型的更改。大緻是這麼寫的:

...
self.linear_weight=torch.nn.Parameter(torch.nn.uniform_(-0.1,0.1)
self.linear_bias=torch.nn.Parameter(torch.zeros(in_dim,hid))
...
           

.data.uniform_(-0.1,0.1)

這個就是個初始化,uniform_指的是均勻分布。

用C++進行pytorch擴充(繼承autograd.Function)

用C進行pytorch擴充(繼承autograd.Function)

啊啊啊,滿地都是坑啊,等着我來填....人生好難我好煩,沖鴨~
           

Python 積累

1. super()

描述

super()函數是調用父類(超類)的一個方法。

super是用來解決多繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒有問題,但是如果使用多繼承,會涉及到查找順序(MRO)、重複調用(鑽石繼承)等種種問題。

MRO就是類的方法解析順序表,其實就是繼承父類方法的順序表。

文法

super(type[,objct-or-type])

參數

type:類

object-or-type:類,一般是self

執行個體

使用執行個體

class A:
     def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        super().add(x)
b = B()
b.add(2)  # 3
           

關于多繼承問題的,super函數使用執行個體:

class FooParent(object):
    def __init__(self):
        self.parent = 'I\'m the parent.'
        print ('Parent')
    
    def bar(self,message):
        print ("%s from Parent" % message)
 
class FooChild(FooParent):
    def __init__(self):
        # super(FooChild,self) 首先找到 FooChild 的父類(就是類 FooParent),然後把類 FooChild 的對象轉換為類 FooParent 的對象
        super(FooChild,self).__init__()    
        print ('Child')
        
    def bar(self,message):
        super(FooChild, self).bar(message)
        print ('Child bar fuction')
        print (self.parent)
 
if __name__ == '__main__':
    fooChild = FooChild()
    fooChild.bar('HelloWorld')
           

即super(FooChild,self)指的就是把現在的FooChild類中的self變成FooChild父類的self,這一行就執行了FooParent的初始化函數。

擴充

class parent():
    def __init__(self):
        print("parent")
        self.hungary=12
    def add(self,x):
        return x+1
class child(parent):
    def __init__(self):
        super(child,self).__init__()
        print("child")
    def add(self,x):
        return x+100

input=0
a=child()
print(a.add(input))
           

輸出為100

是以是優先調用子類中定義的同名函數的。

2. isinstance()

描述

isinstance()函數來判斷一個對象是否是一個已知的類型,類似 type()。

isinstance() 與 type() 差別:

type() 不會認為子類是一種父類類型,不考慮繼承關系。
isinstance() 會認為子類是一種父類類型,考慮繼承關系
           

如果要判斷兩個類型是否相同推薦使用 isinstance()。

文法

isinstance(object,classinfo)

參數

object – 執行個體對象

classinfo – 可以是直接或間接類名、基本類型或者由他們組成的元組

執行個體

>>>a = 2
>>> isinstance (a,int)
True
>>> isinstance (a,str)
False
>>> isinstance (a,(str,int,list))    # 是元組中的一個傳回 True
True
           

type和isinstance的差別:

class A:
    pass
 
class B(A):
    pass
 
isinstance(A(), A)    # returns True
type(A()) == A        # returns True
isinstance(B(), A)    # returns True
type(B()) == A        # returns False
           

繼續閱讀