本文是PyTorch使用過程當中的的一些總結,有如下內容:python
主要涉及到如下函數的使用網絡
add_module
,ModulesList
,Sequential
模型建立modules()
,named_modules()
,children()
,named_children()
訪問模型的各個子模塊parameters()
,named_parameters()
網絡參數的遍歷save()
,load()
,state_dict()
模型的保存與加載torch.nn.Module
是全部網絡的基類,在Pytorch實現的Model都要繼承該類。並且,Module
是能夠包含其餘的Module
的,以樹形的結構來表示一個網絡結構。ide
簡單的定義一個網絡Model
函數
class Model(nn.Module): def __init__(self): super(Model,self).__init__() self.conv1 = nn.Conv2d(3,64,3) self.conv2 = nn.Conv2d(64,64,3) def forward(self,x): x = self.conv1(x) x = self.conv2(x) return x
Model
中兩個屬性conv1
和conv2
是兩個卷積層,在正向傳播的過程當中,再依次調用這兩個卷積層。優化
除了使用Model
的屬性來爲網絡添加層外,還可使用add_module
將網絡層添加到網絡中。google
class Model(nn.Module): def __init__(self): super(Model,self).__init__() self.conv1 = nn.Conv2d(3,64,3) self.conv2 = nn.Conv2d(64,64,3) self.add_module("maxpool1",nn.MaxPool2d(2,2)) self.add_module("covn3",nn.Conv2d(64,128,3)) self.add_module("conv4",nn.Conv2d(128,128,3)) def forward(self,x): x = self.conv1(x) x = self.conv2(x) x = self.maxpool1(x) x = self.conv3(x) x = self.conv4(x) return x
add_module(name,layer)
在正向傳播的過程當中可使用添加時的name
來訪問改layer。url
這樣一個個的添加layer,在簡單的網絡中還行,可是對於負責的網絡層不少的網絡來講就須要敲不少重複的代碼了。 這就須要使用到torch.nn.ModuleList
和torch.nn.Sequential
。code
使用ModuleList
和Sequential
能夠方便添加子網絡到網絡中,可是這二者仍是有所不一樣的。orm
ModuleList
ModuleList
是以list
的形式保存sub-modules
或者網絡層,這樣就能夠先將網絡須要的layer
構建好保存到一個list
,而後經過ModuleList
方法添加到網絡中。對象
class MyModule(nn.Module): def __init__(self): super(MyModule,self).__init__() # 構建layer的list self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)]) def forward(self,x): # 正向傳播,使用遍歷每一個Layer for i, l in enumerate(self.linears): x = self.linears[i // 2](x) + l(x) return x
使用[nn.Linear(10, 10) for i in range(10)]
構建要給Layer的list,而後使用ModuleList
添加到網絡中,在正向傳播的過程當中,遍歷該list
。
更爲方便的是,能夠提早配置後,所須要的各個Layer
的屬性,而後讀取配置建立list
,而後使用ModuleList
將配置好的網絡層添加到網絡中。 以VGG
爲例:
vgg_cfg = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M', 512, 512, 512, 'M'] def vgg(cfg, i, batch_norm=False): layers = [] in_channels = i for v in cfg: if v == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] elif v == 'C': layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)] else: conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) if batch_norm: layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)] else: layers += [conv2d, nn.ReLU(inplace=True)] in_channels = v return layers class Model1(nn.Module): def __init__(self): super(Model1,self).__init__() self.vgg = nn.ModuleList(vgg(vgg_cfg,3)) def forward(self,x): for l in self.vgg: x = l(x) m1 = Model1() print(m1)
讀取配置好的網絡結構vgg_cfg
而後,建立相應的Layer List
,使用ModuleList
加入到網絡中。這樣就能夠很靈活的建立不一樣的網絡。
這裏須要注意的是,ModuleList
是將Module
加入網絡中,須要本身手動的遍歷進行每個Module
的forward
。
Sequential
一個時序容器。Modules 會以他們傳入的順序被添加到容器中。固然,也能夠傳入一個OrderedDict一個時序容器。Modules
會以他們傳入的順序被添加到容器中。固然,也能夠傳入一個OrderedDict
。
Sequential
也是一次加入多個Module到網絡中中,和ModuleList
不一樣的是,它接受多個Module依次加入到網絡中,還能夠接受字典做爲參數,例如:
# Example of using Sequential model = nn.Sequential( nn.Conv2d(1,20,5), nn.ReLU(), nn.Conv2d(20,64,5), nn.ReLU() ) # Example of using Sequential with OrderedDict model = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1,20,5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20,64,5)), ('relu2', nn.ReLU()) ]))
另外一個是,Sequential
中實現了添加Module的forward
,不須要手動的循環調用了。這點相比ModuleList
較爲方便。
常見的有三種方法來添加子Module到網絡中
add_module
方法。ModuleList
能夠將一個Module的List
加入到網絡中,自由度較高,可是須要手動的遍歷ModuleList
進行forward
。Sequential
按照順序將將Module加入到網絡中,也能夠處理字典。 相比於ModuleList
不須要本身實現forward
可使用如下2對4個方法來訪問網絡層全部的Modules
modules()
和 named_modules()
children()
和 named_children()
modules
方法簡單的定義一個以下網絡:
class Model(nn.Module): def __init__(self): super(Model,self).__init__() self.conv1 = nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3) self.conv2 = nn.Conv2d(64,64,3) self.maxpool1 = nn.MaxPool2d(2,2) self.features = nn.Sequential(OrderedDict([ ('conv3', nn.Conv2d(64,128,3)), ('conv4', nn.Conv2d(128,128,3)), ('relu1', nn.ReLU()) ])) def forward(self,x): x = self.conv1(x) x = self.conv2(x) x = self.maxpool1(x) x = self.features(x) return x
modules()
方法,返回一個包含當前模型全部模塊的迭代器,這個是遞歸的返回網絡中的全部Module。使用以下語句
m = Model() for idx,m in enumerate(m.modules()): print(idx,"-",m)
其結果爲:
0 - Model( (conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1)) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1)) (maxpool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (features): Sequential( (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1)) (conv4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1)) (relu1): ReLU() ) ) 1 - Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1)) 2 - Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1)) 3 - MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) 4 - Sequential( (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1)) (conv4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1)) (relu1): ReLU() ) 5 - Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1)) 6 - Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1)) 7 - ReLU()
輸出結果解析:
0-Model
整個網絡模塊1-2-3-4
爲網絡的4個子模塊,注意4 - Sequential
仍然包含有子模塊5-6-7
爲模塊4 - Sequential
的子模塊能夠看出modules()
是遞歸的返回網絡的各個module,從最頂層直到最後的葉子module。
named_modules()
的功能和modules()
的功能相似,不一樣的是它返回內容有兩部分:module的名稱以及module。
children()
方法和modules()
不一樣,children()
只返回當前模塊的子模塊,不會遞歸子模塊。
for idx,m in enumerate(m.children()): print(idx,"-",m)
其輸出爲:
0 - Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1)) 1 - Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1)) 2 - MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) 3 - Sequential( (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1)) (conv4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1)) (relu1): ReLU() )
子模塊3-Sequential
仍然有子模塊,children()
沒有遞歸的返回。
named_children()
和children()
的功能相似,不一樣的是其返回兩部份內容:模塊的名稱以及模塊自己。
方法parameters()
返回一個包含模型全部參數的迭代器。通常用來看成optimizer的參數。
for p in m.parameters(): print(type(p.data),p.size())
其輸出爲:
<class 'torch.Tensor'> torch.Size([128, 64, 3, 3]) <class 'torch.Tensor'> torch.Size([128]) <class 'torch.Tensor'> torch.Size([128, 128, 3, 3]) <class 'torch.Tensor'> torch.Size([128])
包含網絡中的全部的權值矩陣參數以及偏置參數。 對網絡進行訓練時須要將parameters()
做爲優化器optimizer
的參數。
optimizer = torch.optim.SGD(m1.parameters(),lr = args.lr,momentum=args.momentum,weight_decay=args.weight_decay)
parameters()
返回網絡的全部參數,主要是提供給optimizer
用的。而要取得網絡某一層的參數或者參數進行一些特殊的處理(如fine-tuning),則使用named_parameters()
更爲方便些。
named_parameters()
返回參數的名稱及參數自己,能夠按照參數名對一些參數進行處理。
以上面的vgg
網絡爲例:
for k,v in m1.named_parameters(): print(k,v.size())
named_parameters
返回的是鍵值對,k
爲參數的名稱 ,v
爲參數自己。輸出結果爲:
vgg.0.weight torch.Size([64, 3, 3, 3]) vgg.0.bias torch.Size([64]) vgg.2.weight torch.Size([64, 64, 3, 3]) vgg.2.bias torch.Size([64]) vgg.5.weight torch.Size([128, 64, 3, 3]) vgg.5.bias torch.Size([128]) vgg.7.weight torch.Size([128, 128, 3, 3]) vgg.7.bias torch.Size([128]) vgg.10.weight torch.Size([256, 128, 3, 3]) vgg.10.bias torch.Size([256]) vgg.12.weight torch.Size([256, 256, 3, 3]) vgg.12.bias torch.Size([256]) vgg.14.weight torch.Size([256, 256, 3, 3]) vgg.14.bias torch.Size([256]) vgg.17.weight torch.Size([512, 256, 3, 3]) vgg.17.bias torch.Size([512]) vgg.19.weight torch.Size([512, 512, 3, 3]) vgg.19.bias torch.Size([512]) vgg.21.weight torch.Size([512, 512, 3, 3]) vgg.21.bias torch.Size([512]) vgg.24.weight torch.Size([512, 512, 3, 3]) vgg.24.bias torch.Size([512]) vgg.26.weight torch.Size([512, 512, 3, 3]) vgg.26.bias torch.Size([512]) vgg.28.weight torch.Size([512, 512, 3, 3]) vgg.28.bias torch.Size([512])
參數名的命名規則屬性名稱.參數屬於的層的編號.weight/bias
。 這在fine-tuning
的時候,給一些特定的層的參數賦值是很是方便的,這點在後面在加載預訓練模型時會看到。
PyTorch使用torch.save
和torch.load
方法來保存和加載網絡,並且網絡結構和參數能夠分開的保存和加載。
torch.save(model,'model.pth') # 保存 model = torch.load("model.pth") # 加載
torch.save(model.state_dict(),"model.pth") # 保存參數 model = model() # 代碼中建立網絡結構 params = torch.load("model.pth") # 加載參數 model.load_state_dict(params) # 應用到網絡結構中
PyTorch中的torchvision裏有不少經常使用網絡的預訓練模型,例如:vgg
,resnet
,googlenet
等,能夠方便的使用這些預訓練模型進行微調。
# PyTorch中的torchvision裏有不少經常使用的模型,能夠直接調用: import torchvision.models as models resnet101 = models.resnet18(pretrained=True) alexnet = models.alexnet() squeezenet = models.squeezenet1_0()
有時候只須要加載預訓練模型的部分參數,可使用參數名做爲過濾條件,以下
resnet152 = models.resnet152(pretrained=True) pretrained_dict = resnet152.state_dict() """加載torchvision中的預訓練模型和參數後經過state_dict()方法提取參數 也能夠直接從官方model_zoo下載: pretrained_dict = model_zoo.load_url(model_urls['resnet152'])""" model_dict = model.state_dict() # 將pretrained_dict裏不屬於model_dict的鍵剔除掉 pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict} # 更新現有的model_dict model_dict.update(pretrained_dict) # 加載咱們真正須要的state_dict model.load_state_dict(model_dict)
model.state_dict()
返回一個python的字典對象,將每一層與它的對應參數創建映射關係(如model的每一層的weights及偏置等等)。注意,只有有參數訓練的層纔會被保存。
上述的加載方式,是按照參數名類匹配過濾的,可是對於一些參數名稱沒法徹底匹配,或者在預訓練模型的基礎上新添加的一些層,這些層沒法從預訓練模型中獲取參數,須要初始化。
仍然以上述的vgg
爲例,在標準的vgg16
的特徵提取後面,新添加兩個卷積層,這兩個卷積層的參數須要進行初始化。
vgg = torch.load("vgg.pth") # 加載預訓練模型 for k,v in m1.vgg.named_parameters(): k = "features.{}".format(k) # 參數名稱 if k in vgg.keys(): v.data = vgg[k].data # 直接加載預訓練參數 else: if k.find("weight") >= 0: nn.init.xavier_normal_(v.data) # 沒有預訓練,則使用xavier初始化 else: nn.init.constant_(v.data,0) # bias 初始化爲0