Pytorch遷移學習小技巧 以及 Pytorch小技巧的一些總結
遷移學習技巧
內容概要:
- 遷移學習的概念
- Pytorch預訓練模型以及修改
- 不同修改預訓練模型方式的情況
- 一些例子:只針對dense layer的重新訓練 ,凍結初始層的權重重新訓練
遷移學習的概念
神經網路需要用資料來訓練,它從資料中獲得資訊,進而把它們轉換成相應的權重。這些權重能夠被提取出來,遷移到其他的神經網路中,我們“遷移”了這些學來的特徵,就不需要從零開始訓練一個神經網路了 。
Pytorch預訓練模型以及修改
卷積神經網路的訓練是耗時的,很多場合不可能每次都從隨機初始化引數開始訓練網路。
pytorch中自帶幾種常用的深度學習網路預訓練模型,如VGG、ResNet
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
原始碼在Github。 resnet
網路最後一層分類層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下的快兩倍。