深度学习算法应用过程中,数据规模大,训练时间长是我们遇到到一大问题,那么在搭建好深度神经网络模型后,我们还需要大量的算力和时间训练和参数的优化,使得性价比非常低。我们就考虑同类问题的模型迁移,这样提高模型的性价比。考虑刀片没有免费午餐原理,我们尽可能的提高模型的泛化能力以及鲁棒性。需要注意的是,我们在使用迁移学习的过程中有事会导致迁移模型的负迁移,我们可以理解为模型的泛化能力的恶化。当利用同一模型处理两个相关性很小的问题就很容易出现这种情况。
我们将使用kaggle dogs vs.cats数据集进行模型的迁移,首先我们需要对数据集进行处理。
import torch
import torchvision
from torchvision import datasets,models,transforms
import os
import matplotlib.pyplot as plt
from torch.autograd import Variable
import time
#torchvision包主要实现数据的处理,导入和预览,以下数据的导入我们将使用这个包
关于kaggle dogs vs.cats数据集,官网提供的train和test,其中train data包含25000张猫狗的照片,且数量相同。但test data中的猫狗是无序混乱的,且没有label。为了增加模型的泛化能力,我们从训练集中选出一部分做验证集。关于train data ,test data ,valid data的区别下面详细说明一下。
1. 训练数据(Training Set)
用于调整网络的权重(weights)和偏差(biases)。
2. 验证数据(Validation Set)
验证数据用于最小化过拟合(overfitting)。训练完成之后,使用测试数据验证其准确度是否满足要求,即验证其推广/泛化能力。
这数据不调整权重和偏差。在基于训练数据调整权重之后,如果基于训练数据的准确度增加了,而基于验证数据的准确度没有增加或反而下降了,则表明过拟合(overfitting)了,需要立即停止训练。
3. 测试数据(Testing Set)
在训练完成之后,使用测试数据确认网络真正的预测和分类能力。
4. Ground Truth
在有监督学习中,数据是有标注的,以(x, t)的形式出现,其中x是输入数据,t是标注。正确的t标注是Ground Truth, 错误的标记则不是。(也有人将所有标注数据都叫做Ground Truth)
所以为了使得模型具有很高的准确率和低损值,我们将验证集看作模拟的训练集,将测试集看作最终的测试,从而提高泛化能力。
获取全部的数据集之后,我们就可以对这些数据进行简单分类了。新建一个名为DogsVSCats的文件夹,在该文件夹下面新建一个名为train和一个名为valid的子文件夹,在子文件夹下面再分别新建一个名为cat的文件夹和一个名为dog的文件夹,最后将数据集中对应部分的数据放到对应名字的文件夹中,之后就可以进行数据的载入了。
data_dir = "DogsVSCats"
data_transform = {x:transforms.Compose([transforms.Scale([224,224]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5])])
for x in ["train", "valid"]}
image_datasets = {x:datasets.ImageFolder(root = os.path.join(data_dir,x),
transform = data_transform[x])
for x in ["train", "valid"]}
dataloader = {x:torch.utils.data.DataLoader(dataset= image_datasets[x],
batch_size = 16,
shuffle = True)
for x in ["train", "valid"]}
判断是否支持GPU
Use_gpu = torch.cuda.is_available()
以下代码中我们将进行下载的模型是VGG16,并通过设置prepaer=True中的值为Ture,来实现下载的模型附带了已经优化好的
模型参数。
model = models.vgg16(pretrained=True)
第二步需要对迁移的模型进行调整。在迁移学习中全连接层是我们经常调整的部分。思想是冻结卷积神经网络中全连接层之前的所有网络,让这些被冻结的网络层次中的参数不进行梯度更新,能够被优化的参数仅仅是没有被冻结的全连接层的全部参数。
代码调整如下:
or parma in model.parameters():
parma.requires_grad = False
model.classifier = torch.nn.Sequential(torch.nn.Linear(25088, 4096),
torch.nn.ReLU(),
torch.nn.Dropout(p=0.5),
torch.nn.Linear(4096, 4096),
torch.nn.ReLU(),
torch.nn.Dropout(p=0.5),
torch.nn.Linear(4096, 2))
if Use_gpu:
model = model.cuda()
cost = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.classifier.parameters())
loss_f = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.classifier.parameters(), lr = 0.00001)
epoch_n = 5
首先,对原模型中的参数进行遍历操作,将参数中的parma.requires_grad全部设置为False,这样对应的参数将不计算梯度,
当然也不会进行梯度更新了,这就是之前说到的冻结操作;然后,定义新的全连接层结构并重新赋值给model.classifier。在完成了新的全连接层定义后,全连接层中的parma.requires_grad参数会被默认重置为True,所以不需要再次遍历参数来进行解冻操作。损失函数的loss值依然使用交叉熵进行计算,但是在优化函数中负责优化的参数变成了全连接层中的所有参数,即对 model.classifier.parameters这部分参数进行优化。
time_open = time.time()
for epoch in range(epoch_n):
print("Epoch {}/{}".format(epoch, epoch_n - 1))
print("-"*10)
for phase in ["train", "valid"]:
if phase == "train":
print("Training...")
model.train(True)
else:
print("Validing...")
model.train(False)
running_loss = 0.0
running_corrects = 0
for batch, data in enumerate(dataloader[phase], 1):
X, y = data
if Use_gpu:
X, y = Variable(X.cuda()), Variable(y.cuda())
else:
X, y = Variable(X), Variable(y)
y_pred = model(X)
_, pred = torch.max(y_pred.data, 1)
optimizer.zero_grad()
loss = loss_f(y_pred, y)
if phase == "train":
loss.backward()
optimizer.step()
running_loss += loss.data[0]
running_corrects += torch.sum(pred == y.data)
if batch%500 == 0 and phase =="train":
print("Batch {}, Train Loss:{:.4f}, Train ACC:{:.4f}".format(batch, running_loss/batch, 100*running_corrects(16*batch)))
epoch_loss = running_loss*16/len(image_datasets[phase])
epoch_acc = 100*running_corrects/len(image_datasets[phase])
print("{} Loss:{:.4f} Acc:{:.4f}%".format(phase, epoch_loss,epoch_acc))
time_end = time.time() - time_open
print(time_end)