1. 程式人生 > >Pytorch框架下Finetune注意點

Pytorch框架下Finetune注意點

最近在參加AI challenger的比賽(雖然九月就開始的比賽,到11月才開始玩。。。)結局無所謂,就希望在過程中能學習一些東西,由於場景識別比賽的finetune模型權重都是torch下的,之前嘗試了很多權重轉化工具,但是發現基本上都不靠譜,所以比賽要繼續做下去,只能轉向Pytorch,花了整整兩天時間來學習Pytorch,也有了較基礎的一些收穫,現在記錄一下,也和有需要的人一起分享。

1、如何入手

  其實入手的方法有很多,閱讀Pytorch的toturial,然後手冊方面,直接看pytorch的中文文件。然後解決一些基礎的MNIST或者CIFAR10的問題,也可以快速上手,我選擇的也就是利用專案,也就是比賽來快速學習一個工具的使用,但是比賽也需要的一個基礎程式碼來快速學習對吧,我參考的就是這份程式碼

戳我,這個是場景識別Places365資料庫下的一個訓練程式碼,寫的很全,各種metrics的顯示也寫了,比官方toturials要全很多,有興趣的可以看看。
  

2、遷移學習方面

(1)資料擴增

遷移學習一直是深度學習的重要技術方向,這次比賽我只要採用的也是遷移學習的方式。在遷移學習過程中,有需要設定不同層對應不同的學習率,這點keras沒法實現,caffe則可以做到,但是caffe的使用太過於繁瑣。而對於Pytorch而言,遷移學習需要的技術點,它都很完美的吻合了,Pytorch有很多的資料擴增方法,可以很方便的呼叫:

normalize = transforms.Normalize
(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) train_datasets = datasets.ImageFolder(os.path.join(root_dir, train_dir), transforms.Compose([ transforms.RandomResizedCrop(224), #從原影象隨機切割一張(224, 224)的影象 transforms.RandomHorizontalFlip(), #以0.5的概率水平翻轉 transforms.RandomVerticalFlip
(), #以0.5的概率垂直翻轉 transforms.RandomRotation(10), #在(-10, 10)範圍內旋轉 transforms.ColorJitter(0.05, 0.05, 0.05, 0.05), #HSV以及對比度變化 transforms.ToTensor(), #把PIL.Image或者numpy.ndarray物件轉化為tensor,並且是[0,1]範圍,主要是除以255 normalize, ])) train_loader = torch.utils.data.DataLoader(train_datasets, batch_size=64, shuffle=True, num_workers=4, pin_memory=True)

上述程式碼中,pytorch首先通過datasets底下的ImageFolder物件設定影象目錄路徑,以及資料擴增的方式,上述資料擴增的解釋如註釋所示,更詳細的解釋可以看原始碼 ,在圖片路徑設定好之後再通過torch.utils.data下的Dataloader物件來設定資料的生成器,通過簡單兩步就可以設定好一個帶有資料擴增的影象生成器。
在這裡,有一個細節需要注意,ImageFolder物件下的影象路徑應該符合以下格式:

dir/cat/0.jpg
dir/cat/1.jpg
dir/dog/0.jpg
dir/dog/1.jpg

但是像我一開始,使用的目錄格式是這樣的:

dir/0/0.jpg
dir/0/1.jpg
dir/1/0.jpg
dir/1/1.jpg

也就是使用數字代替標籤,這個在caffe裡也是很正常的寫法,但是在之後的predict階段就出現問題了,因為標籤目錄名並不是pytorch真正的類標籤,而是類名,如何獲取類標籤呢?

train_datasets.class_to_idx 

就可以獲取類名和類標籤的一個字典對映,然後通過以下程式碼:

idx_to_class = dict(zip(train_datasets.class_to_idx.values(), train_datasets.class_to_idx.keys()))

就可以通過這個字典來獲得輸出的top精度的標籤所對應的類名。

(2)學習率設定

Pytorch下可以很方便的設定不同層對應不同學習率,比如說對於一個model可以如下設定:

optim.SGD([
                {'params': model.base.parameters()},
                {'params': model.classifier.parameters(), 'lr': 1e-3}
            ], lr=1e-2, momentum=0.9)

這裡程式碼的含義是除了classifier以外的層的學習率都是1e-2。

(3)遷移學習模型新增新層

通常在遷移學習中,都是直接將最後一層的全連線層大小換成自己資料集類的大小,然後finetune,在這次比賽中,我發現這樣的精度並不能提升到最大,因此,採用遷移的base模型來疊加MLP的獲取更高的精度,如何在base模型之後,疊加MLP?程式碼如下:

class model_bn(nn.Module):
    def __init__(self, model, feature_size):

        super(model_bn, self).__init__() 
        self.features = nn.Sequential(*list(model.children())[:-1])
        self.num_ftrs = model.fc.in_features
        self.classifier = nn.Sequential(
            nn.BatchNorm1d(self.num_ftrs),
            nn.Dropout(0.5),
            nn.Linear(self.num_ftrs, feature_size),
            nn.BatchNorm1d(feature_size),
            nn.ELU(inplace=True),
            nn.Dropout(0.5),
            nn.Linear(feature_size, classes_num),
        )
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

在上述程式碼中,self.features = nn.Sequential(*list(model.children())[:-1]) 直接將全base模型中的最後一層全連線層去除,*是解包的操作,將list型別以元組的方式傳遞給nn.Sequential物件,之後新增dropout和bn層來獲取更高的精度,這裡還要注意view函式相當於numpy中的reshape函式,在這裡的作用就是keras中的Flatten層,將輸出從二維壓成一維,在這過程中發現,貌似pytorch沒有直接的全域性池化層,需要自己定義操作,可以通過以下方式來進行:

import torch.nn.functional as F
output = F.average_pool2d(input, kernel_size=input.size()[2:])

(4)雜七雜八的坑

要將模型放入cuda中,才能在gpu中優化執行,之前使用:

model = model.cuda()

無效,但是換成下面程式碼就可以運行了:

model = torch.nn.DataParallel(model).cuda()

在這麼設定之後,又會出現一個問題就是優化器無法獲取到對應的層,也就是說model.fc會報錯,但是仔細觀察不難發現,再將模型放入gpu之後,整體模型被module包裹,所以應該通過model.module.fc.parameters()來獲取對應層的引數。還有就是這樣的設定之後每個batch資料會被平分到所有的gpu上,如果要限制只在一個gpu上執行,則設定即可:

os.environ["CUDA_VISIBLE_DEVICES"] = "2"

最後就是一個數據型別的轉化:
(a)模型的輸出是variable型別,通過.data獲取到tensor型別變數,如果是cuda.tensor,可以通過.cpu()來移動至cpu上。
(b)tensor型別轉化為numpy型別,使用:

b = a.numpy()

反之,使用:

a = torch.from_numpy(a)

最後還有一個將影象的目錄劃分成train和test的方法,這種用法對使用慣了Keras的人來說很常見,而在Pytorch方法中,這個有兩種方式,要不一開始目錄就劃分好,要不然就是動態的通過Pytorch來劃分,具體看這兩個連結:
Pytorch Train Test Split
Train, Validation and Test Split for torchvision Datasets