本次实验主要使用VGG16模型迁移学习进行CIFAR10分类,将猫和狗的图片区分开,平台使用Google Colab平台。
VGG网络
VGG是由Simonyan 和Zisserman在文献《Very Deep Convolutional Networks for Large Scale Image Recognition》中提出卷积神经网络模型,是2014年的 ImageNet图像分类与定位挑战赛的第二名。VGG网络影响巨大,一直到今天仍然很多人在使用。
可以看到VGG网络是一个非常庞大的网络,训练难度也很高,我们本次采用迁移学习的方式,对原网络进行 fine-tune (即固定前面若干层,作为特征提取器,只重新训练最后两层),尝试提高实验效果。
一、数据集导入
研习社为我们提供了提供了训练以及测试的数据集,以及最终实验数据的检测。(目前比赛已经结束,但仍可做为练习赛每天提交测试结果)
由于训练的数据集,我们采用了偷懒的办法,只选取了训练集其中一部分进行训练。
训练集直接从网站链接导入
! wget http://fenggao-image.stor.sinaapp.com/dogscats.zip
! unzip dogscats.zip
测试集数据,下载完成后,解压并仅压缩test文件夹,上传到Google Drive中,然后在colab左侧按钮处连接Google Drive
然后,我们就能在drive目录中查看到我们上传的数据集的压缩包的具体位置。
训练集引入代码:
#具体位置根据自己的情况修改
from google.colab import drive
drive.mount('/content/drive')
!unzip "/content/drive/MyDrive/Colab Notebooks/test.zip"
二、训练模型
1.加载训练集数据
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
vgg_format = transforms.Compose([
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize,
])
data_dir = './dogscats'
dsets = {x: datasets.ImageFolder(os.path.join(data_dir, x), vgg_format)
for x in ['train']}
dset_sizes = {x: len(dsets[x]) for x in ['train']}
dset_classes = dsets['train'].classes
loader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=256, shuffle=True, num_workers=6)
2.下载并加载模型
(1)下载模型
#下载模型
!wget https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json
(2)加载模型
#使用vgg16需要
model_vgg = models.vgg16(pretrained=True)
with open('./imagenet_class_index.json') as f:
class_dict = json.load(f)
dic_imagenet = [class_dict[str(i)][1] for i in range(len(class_dict))]
model_vgg = model_vgg.to(device)
'''
可以看到结果为5行,1000列的数据,每一列代表对每一种目标识别的结果。
但是我也可以观察到,结果非常奇葩,有负数,有正数,
为了将VGG网络输出的结果转化为对每一类的预测概率,我们把结果输入到 Softmax 函数
'''
m_softm = nn.Softmax(dim=1)
def imshow(inp, title=None):
# Imshow for Tensor.
inp = inp.numpy().transpose((1, 2, 0))
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
inp = np.clip(std * inp + mean, 0,1)
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001) # pause a bit so that plots are updated
3.修改模型
主要修改:
(1)冻结VGG16的参数,不进行梯度下降。
(2)增加两个线性层,使用效果更好的参数为0.2的LeakyReLU函数
print(model_vgg)
model_vgg_new = model_vgg;
#冻结VGG16中的参数,不进行梯度下降
for param in model_vgg_new.parameters():
param.requires_grad = False
#新增两个线性层,后期主要训练这两层
model_vgg_new.classifier._modules['6'] = nn.Linear(4096, 4096)
model_vgg_new.classifier._modules['7'] = nn.LeakyReLU(0.2, inplace=True)
model_vgg_new.classifier._modules['8'] = nn.Dropout(p=0.5,inplace=False)
model_vgg_new.classifier._modules['9'] = nn.Linear(4096, 2)
model_vgg_new.classifier._modules['10'] = torch.nn.LogSoftmax(dim=1)
model_vgg_new = model_vgg_new.to(device)
print(model_vgg_new.classifier)
4.训练模型
(1)优化函数由SGD改成了效果更好的Adam。
(2) 每一个epoch结束,都会计算成功率(acc)与目前最高的相比的哪个大,以便取到最好的模型(因为训练模型只是不断趋向于最优,曲线并不严格单调,所以不能保证最后一次的模型是最优解)
#训练模型
from tqdm import trange,tqdm
criterion = nn.NLLLoss()
lr = 0.001
optimizer_vgg = torch.optim.Adam(model_vgg_new.classifier[6].parameters(), lr=lr)
def train_model(model, dataloader, size, epochs=200, optimizer=None):
model.train()
max_acc = 0
count = 0
for epoch in range(epochs):
running_loss = 0.0
running_corrects = 0
count = 0
for inputs, classes in tqdm(dataloader):
inputs = inputs.to(device)
classes = classes.to(device)
outputs = model(inputs)
loss = criterion(outputs, classes)
optimizer = optimizer
optimizer.zero_grad()
loss.backward()
optimizer.step()
_, preds = torch.max(outputs.data, 1)
# statistics
running_loss += loss.data.item()
running_corrects += torch.sum(preds == classes.data)
count += len(inputs)
#print('Training: No. ', count, ' process ... total: ', size)
epoch_loss = running_loss / size
epoch_acc = running_corrects.data.item() / size
if epoch_acc>max_acc:
max_acc = epoch_acc
torch.save(model, '/content/drive/MyDrive/Colab Notebooks/model_best_new.pth')
tqdm.write("\n Got A Nice Model Acc:{:.8f}".format(max_acc))
tqdm.write('\nepoch: {} \tLoss: {:.8f} Acc: {:.8f}'.format(epoch,epoch_loss, epoch_acc))
time.sleep(0.1)
torch.save(model, '/content/drive/MyDrive/Colab Notebooks/model.pth')
tqdm.write("Got A Nice Model")
# 模型训练
train_model(model_vgg_new, loader_train, size=dset_sizes["train"], epochs=100,
optimizer=optimizer_vgg)
在训练之后,将最佳模型以pth格式保存在Google Drive上。
三、测试模型
由于网络问题,导致Colab有时会出现掉线的问题,有时会掉线导致训练模型丢失,所以采用将训练和测试分离的方法,通过pth文件读取传输模型数据。
1.加载测试数据和已训练好的模型
此处我们已经完成了数据的读取和解压,应该已经存在一个解压好的test文件夹。
import torch
import numpy as np
from torchvision import transforms,datasets
from tqdm import tqdm
device = torch.device("cuda:0" )
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
vgg_format = transforms.Compose([
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize,
])
dsets_mine = datasets.ImageFolder(r"test1", vgg_format)
loader_test = torch.utils.data.DataLoader(dsets_mine, batch_size=1, shuffle=False, num_workers=0)
model_vgg_new = torch.load(r'/content/drive/MyDrive/Colab Notebooks/model_best_new.pth')
model_vgg_new = model_vgg_new.to(device)
这里有一个小坑,如果在读取文件夹处使用test,就会报错Found 0 files in subfolders ,因为ImageFolder()的读取方式是,在该文件夹内的文件夹中读取文件。所以,我们此处在外层建立一个test1文件夹,把test放入test1文件夹中。
2.测试,获取数据,写入文件
(1)测试
dic = {}
def test(model,dataloader,size):
model.eval()
predictions = np.zeros(size)
cnt = 0
for inputs,_ in tqdm(dataloader):
inputs = inputs.to(device)
outputs = model(inputs)
_,preds = torch.max(outputs.data,1)
#这里是切割路径,因为dset中的数据不是按1-2000顺序排列的
key = dsets_mine.imgs[cnt][0].split("\\")[-1].split('.')[0]
dic[key] = preds[0]
cnt = cnt +1
test(model_vgg_new,loader_test,size=2000)
(2)写入文件
with open("result.csv",'a+') as f:
for key in range(2000):
f.write("{},{}\n".format(key,dic["test1/test/"+str(key)]))
根据研习社的测试要求,将测试结果写入csv文件夹
四、结果
研习社网站得到的测试结果为97.75,结果不错,但还有很大的进步空间,可以进一步改造模型,或者更直接的方法,多跑几遍,多试几遍的方法,提高正确率。
源码:
(1)训练:https://colab.research.google.com/drive/1W1KCgB0u05ZgvoivOHL68dzvlO3nDD7P#scrollTo=ZDj0OVrPnD1C
(2)测试:
https://colab.research.google.com/drive/1sYqR8fwUflqKPe5WwjAnJ-bgnK1L8vl6#scrollTo=VFhys8h3nrBd
参考:
[1]:https://github.com/dataflowr/notebooks/blob/master/CEA_EDF_INRIA/01_intro_DLDIY_colab.ipynb
[2]:https://blog.csdn.net/Vision_Tung/article/details/109697509#%E7%8C%AB%E7%8B%97%E5%A4%A7%E6%88%98–%E4%BD%BF%E7%94%A8%20%E2%80%9CVGG16%E8%BF%9B%E8%A1%8CCIFAR10%E5%88%86%E7%B1%BB%E2%80%9D%20%C2%A0%E8%BF%81%E7%A7%BB%E5%AD%A6%E4%B9%A0%E5%AE%9E%E7%8E%B0-toc