Pytorch遷移學習小技巧 以及 Pytorch小技巧的一些總結

遷移學習技巧

內容概要:

  • 遷移學習的概念
  • Pytorch預訓練模型以及修改
  • 不同修改預訓練模型方式的情況
  • 一些例子:只針對dense layer的重新訓練 ,凍結初始層的權重重新訓練

遷移學習的概念

神經網絡需要用數據來訓練,它從數據中獲得信息,進而把它們轉換成相應的權重。這些權重能夠被提取出來,遷移到其他的神經網絡中,我們“遷移”了這些學來的特徵,就不需要從零開始訓練一個神經網絡了 。

Pytorch預訓練模型以及修改

卷積神經網絡的訓練是耗時的,很多場合不可能每次都從隨機初始化參數開始訓練網絡。
pytorch中自帶幾種常用的深度學習網絡預訓練模型,如VGG、ResNet等。往往爲了加快學習的進度,在訓練的初期我們直接加載pre-train模型中預先訓練好的參數,model的加載如下所示:

import torchvision.models as models  

#resnet  
model = models.ResNet(pretrained=True)  
model = models.resnet18(pretrained=True)  
model = models.resnet34(pretrained=True)  
model = models.resnet50(pretrained=True)  

#vgg  
model = models.VGG(pretrained=True)  
model = models.vgg11(pretrained=True)  
model = models.vgg16(pretrained=True)  
model = models.vgg16_bn(pretrained=True)  

預訓練模型的修改(具體何種情況下需要用到哪種修改方式,我們後面說)
1. 參數修改
對於簡單的參數修改,這裏以resnet預訓練模型舉例,resnet源代碼在Githubresnet網絡最後一層分類層fc是對1000種類型進行劃分,對於自己的數據集,如果只有9類,修改的代碼如下:

# coding=UTF-8  
import torchvision.models as models  

#調用模型  
model = models.resnet50(pretrained=True)  
#提取fc層中固定的參數  
fc_features = model.fc.in_features  
#修改類別爲9  
model.fc = nn.Linear(fc_features, 9)  

2. 增減卷積層
前一種方法只適用於簡單的參數修改,有的時候我們往往要修改網絡中的層次結構,這時只能用參數覆蓋的方法,即自己先定義一個類似的網絡,再將預訓練中的參數提取到自己的網絡中來。這裏以resnet預訓練模型舉例。

# coding=UTF-8  
import torchvision.models as models  
import torch  
import torch.nn as nn  
import math  
import torch.utils.model_zoo as model_zoo  

class CNN(nn.Module):  

    def __init__(self, block, layers, num_classes=9):  
        self.inplanes = 64  
        super(ResNet, self).__init__()  
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,  
                               bias=False)  
        self.bn1 = nn.BatchNorm2d(64)  
        self.relu = nn.ReLU(inplace=True)  
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)  
        self.layer1 = self._make_layer(block, 64, layers[0])  
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)  
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)  
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)  
        self.avgpool = nn.AvgPool2d(7, stride=1)  
        #新增一個反捲積層  
        self.convtranspose1 = nn.ConvTranspose2d(2048, 2048, kernel_size=3, stride=1, padding=1, output_padding=0, groups=1, bias=False, dilation=1)  
        #新增一個最大池化層  
        self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)  
        #去掉原來的fc層,新增一個fclass層  
        self.fclass = nn.Linear(2048, num_classes)  

        for m in self.modules():  
            if isinstance(m, nn.Conv2d):  
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels  
                m.weight.data.normal_(0, math.sqrt(2. / n))  
            elif isinstance(m, nn.BatchNorm2d):  
                m.weight.data.fill_(1)  
                m.bias.data.zero_()  

    def _make_layer(self, block, planes, blocks, stride=1):  
        downsample = None  
        if stride != 1 or self.inplanes != planes * block.expansion:  
            downsample = nn.Sequential(  
                nn.Conv2d(self.inplanes, planes * block.expansion,  
                          kernel_size=1, stride=stride, bias=False),  
                nn.BatchNorm2d(planes * block.expansion),  
            )  

        layers = []  
        layers.append(block(self.inplanes, planes, stride, downsample))  
        self.inplanes = planes * block.expansion  
        for i in range(1, blocks):  
            layers.append(block(self.inplanes, planes))  

        return nn.Sequential(*layers)  

    def forward(self, x):  
        x = self.conv1(x)  
        x = self.bn1(x)  
        x = self.relu(x)  
        x = self.maxpool(x)  

        x = self.layer1(x)  
        x = self.layer2(x)  
        x = self.layer3(x)  
        x = self.layer4(x)  

        x = self.avgpool(x)  
        #新加層的forward  
        x = x.view(x.size(0), -1)  
        x = self.convtranspose1(x)  
        x = self.maxpool2(x)  
        x = x.view(x.size(0), -1)  
        x = self.fclass(x)  

        return x  

#加載model  
resnet50 = models.resnet50(pretrained=True)  
cnn = CNN(Bottleneck, [3, 4, 6, 3])  
#讀取參數  
pretrained_dict = resnet50.state_dict()  
model_dict = cnn.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  
cnn.load_state_dict(model_dict)  
# print(resnet50)  
print(cnn)  

不同修改預訓練模型方式的情況

1. 特徵提取
我們可以將預訓練模型當做特徵提取裝置來使用。具體的做法是,將輸出層去掉,然後將剩下的整個網絡當做一個固定的特徵提取機,從而應用到新的數據集中。

2. 採用預訓練模型的結構
我們還可以採用預訓練模型的結構,但先將所有的權重隨機化,然後依據自己的數據集進行訓練。

3. 訓練特定層,凍結其它層
另一種使用預訓練模型的方法是對它進行部分的訓練。具體的做法是,將模型起始的一些層的權重保持不變,重新訓練後面的層,得到新的權重。在這個過程中,我們可以多次進行嘗試,從而能夠依據結果找到frozen layers和retrain layers之間的最佳搭配。
如何使用與訓練模型,是由數據集大小和新舊數據集(預訓練的數據集和我們要解決的數據集)之間數據的相似度來決定的。
下圖表展示了在各種情況下應該如何使用預訓練模型:
這裏寫圖片描述

一些例子

這個例子也是我從量子位上面看來的,感覺很有借鑑意義。他這個原先是我曾經使用vgg16作爲預訓練的模型結構,並把它應用到手寫數字識別上。嘗試了兩種方法。
1. 只重新訓練輸出層 & dense layer
這裏我們採用vgg16作爲特徵提取器。隨後這些特徵,會被傳遞到依據我們數據集訓練的dense layer上(這裏需要注意的是dense layer其實就是常用的全連接層,所實現的運算是output = activation(dot(input, kernel)+bias)。其中activation是逐元素計算的激活函數,kernel是本層的權值矩陣,bias爲偏置向量,只有當use_bias=True纔會添加)。輸出層同樣由與我們問題相對應的softmax層函數所取代。
vgg16中,輸出層是一個擁有1000個類別的softmax層。我們把這層去掉,換上一層只有10個類別的softmax層。我們只訓練這些層,然後就進行數字識別的嘗試。

# importing required librariesfrom keras.models import Sequentialfrom scipy.misc import imread
get_ipython().magic('matplotlib inline')import matplotlib.pyplot as pltimport numpy as npimport kerasfrom keras.layers import Denseimport pandas as pdfrom keras.applications.vgg16 import VGG16from keras.preprocessing import imagefrom keras.applications.vgg16 import preprocess_inputimport numpy as npfrom keras.applications.vgg16 import decode_predictions
train=pd.read_csv("R/Data/Train/train.csv")
test=pd.read_csv("R/Data/test.csv")
train_path="R/Data/Train/Images/train/"test_path="R/Data/Train/Images/test/"from scipy.misc import imresize# preparing the train datasettrain_img=[]for i in range(len(train)):

    temp_img=image.load_img(train_path+train['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    train_img.append(temp_img)#converting train images to array and applying mean subtraction processingtrain_img=np.array(train_img) 
train_img=preprocess_input(train_img)# applying the same procedure with the test datasettest_img=[]for i in range(len(test)):

    temp_img=image.load_img(test_path+test['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    test_img.append(temp_img)

test_img=np.array(test_img) 
test_img=preprocess_input(test_img)# loading VGG16 model weightsmodel = VGG16(weights='imagenet', include_top=False)# Extracting features from the train dataset using the VGG16 pre-trained modelfeatures_train=model.predict(train_img)# Extracting features from the train dataset using the VGG16 pre-trained modelfeatures_test=model.predict(test_img)# flattening the layers to conform to MLP inputtrain_x=features_train.reshape(49000,25088)# converting target variable to arraytrain_y=np.asarray(train['label'])# performing one-hot encoding for the target variabletrain_y=pd.get_dummies(train_y)
train_y=np.array(train_y)# creating training and validation setfrom sklearn.model_selection import train_test_split
X_train, X_valid, Y_train, Y_valid=train_test_split(train_x,train_y,test_size=0.3, random_state=42)# creating a mlp modelfrom keras.layers import Dense, Activation
model=Sequential()

model.add(Dense(1000, input_dim=25088, activation='relu',kernel_initializer='uniform'))
keras.layers.core.Dropout(0.3, noise_shape=None, seed=None)

model.add(Dense(500,input_dim=1000,activation='sigmoid'))
keras.layers.core.Dropout(0.4, noise_shape=None, seed=None)

model.add(Dense(150,input_dim=500,activation='sigmoid'))
keras.layers.core.Dropout(0.2, noise_shape=None, seed=None)

model.add(Dense(units=10))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])# fitting the model model.fit(X_train, Y_train, epochs=20, batch_size=128,validation_data=(X_valid,Y_valid))

2. 凍結最初的幾層網絡權重
還有一種方案是這裏我們將會把vgg16網絡的前8層進行凍結,然後對後面的網絡重新進行訓練。這麼做是因爲最初的幾層網絡捕獲的是曲線、邊緣這種普遍的特徵,這跟我們的問題是相關的。我們想要保證這些權重不變,讓網絡在學習過程中重點關注這個數據集特有的一些特徵,從而對後面的網絡進行調整。

from keras.models import Sequentialfrom scipy.misc import imread
get_ipython().magic('matplotlib inline')import matplotlib.pyplot as pltimport numpy as npimport kerasfrom keras.layers import Denseimport pandas as pdfrom keras.applications.vgg16 import VGG16from keras.preprocessing import imagefrom keras.applications.vgg16 import preprocess_inputimport numpy as npfrom keras.applications.vgg16 import decode_predictionsfrom keras.utils.np_utils import to_categoricalfrom sklearn.preprocessing import LabelEncoderfrom keras.models import Sequentialfrom keras.optimizers import SGDfrom keras.layers import Input, Dense, Convolution2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Dropout, Flatten, merge, Reshape, Activationfrom sklearn.metrics import log_loss

train=pd.read_csv("R/Data/Train/train.csv")
test=pd.read_csv("R/Data/test.csv")
train_path="R/Data/Train/Images/train/"test_path="R/Data/Train/Images/test/"from scipy.misc import imresize

train_img=[]for i in range(len(train)):

    temp_img=image.load_img(train_path+train['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    train_img.append(temp_img)

train_img=np.array(train_img) 
train_img=preprocess_input(train_img)

test_img=[]for i in range(len(test)):

temp_img=image.load_img(test_path+test['filename'][i],target_size=(224,224))

    temp_img=image.img_to_array(temp_img)

    test_img.append(temp_img)

test_img=np.array(test_img) 
test_img=preprocess_input(test_img)from keras.models import Modeldef vgg16_model(img_rows, img_cols, channel=1, num_classes=None):

    model = VGG16(weights='imagenet', include_top=True)

    model.layers.pop()

    model.outputs = [model.layers[-1].output]

    model.layers[-1].outbound_nodes = []

          x=Dense(num_classes, activation='softmax')(model.output)

    model=Model(model.input,x)#To set the first 8 layers to non-trainable (weights will not be updated)

          for layer in model.layers[:8]:

       layer.trainable = False# Learning rate is changed to 0.001
    sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
    model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])    return model

train_y=np.asarray(train['label'])

le = LabelEncoder()

train_y = le.fit_transform(train_y)

train_y=to_categorical(train_y)

train_y=np.array(train_y)from sklearn.model_selection import train_test_split
X_train, X_valid, Y_train, Y_valid=train_test_split(train_img,train_y,test_size=0.2, random_state=42)# Example to fine-tune on 3000 samples from Cifar10img_rows, img_cols = 224, 224 # Resolution of inputschannel = 3num_classes = 10 batch_size = 16 nb_epoch = 10# Load our modelmodel = vgg16_model(img_rows, img_cols, channel, num_classes)

Pytorch技巧

之前採用keras進行深度學習網絡的搭建,取得的效果還不錯,但是有一些參數沒法更改,同時一些參數也沒法實時看到。在師兄的指引下,決定採用pytorch進行深度學習模型的搭建。開始的時候也不是很順,主要還是自主探索新領域的難吧。

內容概要:

  • windows環境下pytorch和tensorboard聯合使用
  • 修改預訓練模型

windows環境下pytorch和tensorboard聯合使用

參考鏈接

過程:

  • 運行主函數 ,然後產生logs文件
  • 切換到board目錄下,運行:tensorboard --logdir=./logs --port=6006
  • 在瀏覽器地址欄中輸入 http://localhost:6006/

修改預訓練模型

這個比如說Faster-RCNN基於vgg19提取features,但是隻使用了一部分模型提取features,所以需要知道如何修改預訓練模型,參考鏈接

步驟:

  • 下載vgg19的pth文件,在anaconda中直接設置pretrained=True下載一般都比較慢,我用的瀏覽器或者迅雷直接下載,在model_zoo裏面有各種預訓練模型的下載鏈接
model_urls = {
    'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth',
    'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth',
    'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth',
    'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth',
    'vgg11_bn': 'https://download.pytorch.org/models/vgg11_bn-6002323d.pth',
    'vgg13_bn': 'https://download.pytorch.org/models/vgg13_bn-abd245e5.pth',
    'vgg16_bn': 'https://download.pytorch.org/models/vgg16_bn-6c64b313.pth',
    'vgg19_bn': 'https://download.pytorch.org/models/vgg19_bn-c79401a0.pth',
  • 下載好的模型,可以用下面這段代碼試着看一下,並且改一下模型。在vgg19.pth同級目錄建立一個test.py
import torch
import torch.nn as nn
import torchvision.models as models

vgg16 = models.vgg16(pretrained=False)
vgg16.load_state_dict(torch.load('vgg16-397923af.pth'))
print('vgg16:\n', vgg16)

modified_features = nn.Sequential(*list(vgg16.features.children())[:-1])
# to relu5_3
print('modified_features:\n', modified_features )

具體地,模型的保存與加載,Pytorch有兩種方式,上面代碼是其中一種方式。
torch.save()實現對網絡結構和模型參數的保存。有兩種保存方式:
一、是保存年整個神經網絡的的結構信息和模型參數信息save的對象是網絡net
二、是隻保存神經網絡的訓練模型參數,save的對象是net.state_dict()

torch.save(net1, '7-net.pth')                     # 保存整個神經網絡的結構和模型參數    
torch.save(net1.state_dict(), '7-net_params.pth') # 只保存神經網絡的模型參數 

對應上面兩種保存方式,重載方式也有兩種。
對應第一種完整網絡結構信息,重載的時候通過torch.load(‘.pth’)直接初始化新的神經網絡對象即可。
對應第二種只保存模型參數信息,需要首先導入對應的網絡,通過net.load_state_dict(torch.load('.pth'))完成模型參數的重載。在網絡比較大的時候,第一種方法會花費較多的時間。

# 保存和加載整個模型  
torch.save(model_object, 'model.pkl')  
model = torch.load('model.pkl')  
# 僅保存和加載模型參數(推薦使用)  
torch.save(model_object.state_dict(), 'params.pkl')  
model_object.load_state_dict(torch.load('params.pkl'))  
  • 修改好之後features就可以拿去做Faster-RCNN提取特徵用了。

注意 在Linux系統之下,運行速度是Windows下的快兩倍。


參考文獻:

  1. pytorch小技巧
  2. pytorch中的pre-train函數模型引用及修改(增減網絡層,修改某層參數等)
  3. PyTorch中使用預訓練的模型初始化網絡的一部分參數
  4. PyTorch預訓練
  5. Pytorch使用總結
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章