上一小節講的是怎么自定義初始化參數。
這一節是看怎么自定義層。
這樣可以想一下之前接觸的樓層是什么。比如nn.Linear
,nn.ReLU
等。他們的作用就是作為某一層的處理。他們兩個的區別在于前者有參數,后者是沒有參數列表的。那現在我們也來實現一些有參數和沒有參數列表的層操作。
import torchimport torch.nn.functional as Ffrom torch import nn
不帶參數的層
class CenteredLayer(nn.Module): def __init__(self): super().__init__() def forward(self, X): return X - X.mean()
我們也只需要定義前向傳播就可以了。這個自建一層的作用是讓每一個特征量都減去其平均值。
layer = CenteredLayer()X = torch.arange(5)*0.1print(layer(X))
>>tensor([-0.2000, -0.1000, 0.0000, 0.1000, 0.2000])
經過測試我們可以看到這個層是完全有效的。
那如果將其放到復雜的模型之中呢。
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())Y = torch.rand(10, 8)print(net(Y).mean().data)
>>tensor(7.8231e-09)
好吧,這個模型其實并不復雜,它只有兩層。第一個是一個線性層。第二個就是我們的自定義層。
生成一組隨機的測試數據Y。然后使用我們構建的網絡對外進行計算,然后輸出其結果的平均值。
不出意外結果應該是0。雖然這里顯示的不是0。這是因為浮點數的存儲精度問題,你當然可以把這個極小的數近似看作它是0。
至于結果為什么失靈,這是一個數學問題,會去列幾個數字自己算一下就明白了。
帶參數的層
class MyLinear(nn.Module): def __init__(self, in_units, units): super().__init__() self.weight = nn.Parameter(torch.ones(in_units, units)) self.bias = nn.Parameter(torch.zeros(units,)) def forward(self, X): linear = torch.matmul(X, self.weight.data) + self.bias.data return F.relu(linear)
這個租賃一層是自定義實現了一個全鏈接層。這個層里的參數需要用到權重和偏置,在計算之后最后返回再使用ReLU激活函數。
linear = MyLinear(5, 3)print(linear.weight.data)
>>tensor([[ 1.0599, 0.3885, 1.2025], [-1.8313, 0.2097, -1.6529], [ 1.4119, 0.2675, -0.4148], [ 0.2596, -0.0319, 1.9548], [-1.2874, 1.0776, 0.5804]])
輸出它的權重看一下,確實是能生成5×3的權重矩陣。
X = torch.rand(2, 5)linear(X)
>>tensor([[2.3819, 2.3819, 2.3819], [1.8295, 1.8295, 1.8295]])
單層測試結果也沒有問題。
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))net(torch.rand(2, 64))
>>tensor([[0.4589], [0.0000]])
將其放在網絡中結果也沒有問題。
現在我來放一段對比代碼,就是我們自己寫的這個層和pytorch人家寫的層該怎么實現同樣的功能。
net1 = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))net2 = nn.Sequential(nn.Linear(64,8), nn.ReLU(), nn.Linear(8,1), nn.ReLU())def init(m): if type(m)==nn.Linear: nn.init.ones_(m.weight) nn.init.zeros_(m.bias)net2.apply(init)Y = torch.rand(4, 64)print(net1(Y).data)print(net2(Y).data)
>>tensor([[270.5055], [253.7892], [238.7834], [258.4998]])tensor([[270.5055], [253.7892], [238.7834], [258.4998]])
這樣乍一看是不是兩個結果完全一樣。
相對于pytorch自帶的實現來說,這個不需要你寫一個加權重的過程,也不需要你再加一個ReLU層。
這樣看起來很省事,但是實際中不建議你自己實現pytorch之中已經有的功能。因為使用人家的方法計算效率更高。