Pytorch框架下Finetune注意點
最近在參加AI challenger的比賽(雖然九月就開始的比賽,到11月才開始玩。。。)結局無所謂,就希望在過程中能學習一些東西,由於場景識別比賽的finetune模型權重都是torch下的,之前嘗試了很多權重轉化工具,但是發現基本上都不靠譜,所以比賽要繼續做下去,只能轉向Pytorch,花了整整兩天時間來學習Pytorch,也有了較基礎的一些收穫,現在記錄一下,也和有需要的人一起分享。
1、如何入手
其實入手的方法有很多,閱讀Pytorch的toturial,然後手冊方面,直接看pytorch的中文文件。然後解決一些基礎的MNIST或者CIFAR10的問題,也可以快速上手,我選擇的也就是利用專案,也就是比賽來快速學習一個工具的使用,但是比賽也需要的一個基礎程式碼來快速學習對吧,我參考的就是這份程式碼
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