需要從PointNet網絡框架中提取encoder部分的參數,然后賦予自己的模型。因此,需要從一個已有的.pth
文件讀取部分參數,加載到自定義模型上面。做了一些嘗試,記錄如下。
關于模型保存與載入
torch.save()
: 使用Python的pickle實用程序將對象進行序列化,然后將序列化的對象保存到disk,可以保存各種對象,包括模型、張量和字典等。
torch.load()
: 使用pickle unpickle工具將pickle的對象文件反序列化為內存。
可以看出,pth文件本質上是一個序列化的dict。
我們在save時,代碼如下:
state = {'epoch': epoch,'model_state_dict': model.state_dict(),'optimizer_state_dict': optimizer.state_dict(),
}
然后以下代碼load進來:
checkpoint = torch.load(args.model_file, map_location=device)
model.load_state_dict(checkpoint['model_state_dict'])
查看checkpoint,可以看到包含的就是自己保存時的3個dict,分別是epoch,model_state_dict,和optimizer信息。
這里我們重點關注 model_state_dict,數據類型是一個 OrderedDict
,有序字典。展開如下:
可以看到里面包含了自己定義的encoder,bn1-3,mlp 1-4層,以及每個層對應的參數(權重、bias,對于bn層還有mean, var等)。
這個Dict的順序就是在Model中我們定義的順序,這個和模型是一致的。
因此,如果載入時的模型和保存模型完全一致,直接用load_state_dict()
就可以按順序把數據載入進來。但如,如果定義不同怎么辦?這就需要手動載入。
方法1:手動載入指定層的參數
從debug的斷點可以看到,每個參數就是存在dict中的一個tensor。因此,我們只要讀取對應的dict即可。
例如,encoder的conv1的權重,就是 checkpoint['model_state_dict']['encoder.conv1.weight']
,那么我們在自己的模型對應的位置讀取這個dict即可。
具體載入方式如下:
# 定義模型
model = MyPointNetSegmentation(channel=3, get_feature=True, batch_size=1)
model.to('cpu')# 載入其他模型的參數
checkpoint = torch.load(model_file, map_location='cpu')
model_dict = checkpoint['model_state_dict']# 將其他模型的參數,賦值給自己模型對應參數
model.encoder.conv1.weight.data.copy_(model_dict['encoder.conv1.weight'])
model.encoder.conv1.bias.data.copy_(model_dict['encoder.conv1.bias'])
把所有有用的參數都賦值過來就好,但要注意參數對應的tensor維度是一樣的。
方法2:一次性載入key值相同的參數
如果說兩個model的某些key值相同,可以用python的字典推導方式,將名稱相關的參數提取出來。例如:
def load_dict_from_pointnet(model : Point2VoxelNet, checkpoint):my_model_dict = model.state_dict()pretrained_dict = checkpoint['model_state_dict']# 只將pretraind_dict中那些在model_dict中的參數,提取出來state_dict = {k:v for k,v in pretrained_dict.items() if k in my_model_dict .keys()}my_model_dict.update(state_dict) # 注意要更新state的變量,如果直接賦值,會出現某些key沒有定義,導致運行失敗model.load_state_dict(my_model_dict)# 對比參數是否一致print(f"{checkpoint['model_state_dict']['feat.stn.conv1.weight'][1]}")print(f"{model.feat.stn.conv1.weight[1]}")return model
看到這里,可以知道如果自己的模型改了名稱,例如.pth
的參數是:feat.stn.conv1
,我這邊叫做了 encoder.stn.conv1
,那么是無法直接賦值的。可以用方法1,一個個載入,但是太慢了。另一種方式,是做一個鍵值映射,如果讀到的是 feat.xxx
,則賦予自定義模型中的 encoder.xxx
,簡單處理即可。
注意事項
- conv層需要載入的參數有:weight 和 bias
- BN層涉及的參數有:
- weight,bias
- running_mean,running_var:這兩個參數用于歸一化的均值和方差, 因此也需要載入
- num_batches_tracked:在訓練時需要載入,在test時不需要載入
- 載入參數后,如果用于測試,需要調用
eval()
。注意不能在載入參數前調用 eval。eval 會將 bn 層的training
參數設置為 false ,這樣在測試時 batch_size 時如果是 1 也能夠正常運行。
測試
用默認方式載入參數,以及手動方式載入后的兩個模型,預測結果一致。