本周主要学习了几种经典的现代神经网络:AlexNet,VGG,NiN,GeoogleNet、ResNet。
这几种网络可以看作是神经网络发展过程中不断去精进的一个过程:
从开始的LeNet网络,通过更改激活函数,使用DropOut等方法产生了AlexNet;
在引入了块的概念,通过构建VGG块,将网络实现规整化的VGGNet;
为了减少参数,使用两个1*1卷积层来代替全连接层的NiN;
再到结合多张路径的Inception实现的GoogleNet;
再到把输入参数考虑进去的ResNet;
这个变化是将网络变得更深,变得更优化,从而获得更好的实现性能。
跟李沐学AI-动手学深度学习-现代神经网络
首先进行的是对老师给的使用VGG进行迁移学习实现的猫狗大战分类的代码学习。
通过学习整理出代码思路,然后进行进一步的模型的修改。
数据的下载: https://god.yanxishe.com/8
数据预处理: 使用transforms进行切割大小为224*224,并进行CHW格式转换。
数据本地操作: 训练数据为900张猫900张狗的图片,将数据设置为train/dog和 train/cat的架构。
迁移学习: 使用vgg16进行操作,固定住前边的卷积,训练后边的Linear,最后进行softmax操作。
整体的思路和上边使用vgg进行迁移学习的思路相似。
代码实现主要参考了VGG的迁移学习和博客:https://www.cnblogs.com/Arsene-W/p/13377011.html
#1.导入包、设置gpu/cpu、设定随机种子
import numpy as np
import matplotlib.pyplot as plt
import os
import torch
import torch.nn as nn
import torchvision
from torchvision import models,transforms,datasets
import time
import json
import shutil
from PIL import Image
import csv
# 判断是否存在GPU设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Using gpu: %s ' % torch.cuda.is_available())
# 设置随机种子,方便复现
torch.manual_seed(10000) # 为CPU设置随机种子
torch.cuda.manual_seed(10000) # 为当前GPU设置随机种子
torch.cuda.manual_seed_all(10000) # 为所有GPU设置随机种子
#3.载入数据集,并对数据进行处理
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
#功能:逐channel的对图像进行标准化(均值变为0,标准差变为1),可以加快模型的收敛。
#mean:各通道的均值,std:各通道的标准差,inplace:是否原地操作。
#思考疑问:为啥mean和std里边的数值是这样的?这一组数据是从ImageNet训练集中抽样算出来的。
resnet_format = transforms.Compose([
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize,
])
#torchvision.transforms.Compose()类,主要作用是串联多个图片变换的操作。
#这个类的构造很简单:
#transforms.CenterCrop主要是用来将图像从中心裁剪成224*224
#transforms.ToTensor主要是:先由HWC转置为CHW格式、再转为float类型、最后,每个像素除以255。
data_dir = r'F:\1_yan\cat_dog\catsdogs'
dsets = {x: datasets.ImageFolder(os.path.join(data_dir, x), resnet_format)
for x in ['train', 'val']}
#datasets.ImageFolder(root,transform,target_transform,loader,is_valid_file)
#root:图片存储的根目录,即各类别文件夹所在目录的上一级目录。
#transform:对图片进行预处理的操作(函数),原始图片作为输入,返回一个转换后的图片。
#target_transform:对图片类别进行预处理的操作,输入为target,输出对其的转换。如果不传该参数,即对target不做任何转换,返回的顺序索引0,1,2,....
#loader:表示数据集的加载方式,通常默认加载方式即可。
#is_valid_file:获取图像文件的路径并检查该文件是否为有效文件的函数(即用于检查损坏文件)。
dset_sizes = {x: len(dsets[x]) for x in ['train', 'val']}
dset_classes = dsets['train'].classes
#resnet50下,需要显存太大,将batch size调小为5
loader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=5, shuffle=True, num_workers=4)
loader_valid = torch.utils.data.DataLoader(dsets['val'], batch_size=5, shuffle=False, num_workers=4)
#4.载入ResNet152并修改模型全连接层
#model = models.resnet152(pretrained=True)
model = models.resnet50(pretrained=True)
model_new = model.to(device);
model_new.fc = nn.Linear(2048, 2,bias=True)
model_new = model_new.to(device)
print(model_new)
#部分参数
#采用交叉熵损失函数
criterion = nn.CrossEntropyLoss()
#该损失函数结合了nn.LogSoftmax()和nn.NLLLoss()两个函数。它在做分类(具体几类)训练的时候是非常有用的。
# 学习率0.001,每10epoch *0.1
lr = 0.001
# 随机梯度下降,momentum加速学习,Weight decay防止过拟合
optimizer = torch.optim.SGD(model_new.parameters(), lr=lr, momentum=0.9, weight_decay=5e-4)
#5.模型训练
def val_model(model,dataloader,size):
model.eval()
predictions = np.zeros(size)
all_classes = np.zeros(size)
all_proba = np.zeros((size,2))
i = 0
running_loss = 0.0
running_corrects = 0
with torch.no_grad():
for inputs,classes in dataloader:
inputs = inputs.to(device)
classes = classes.to(device)
outputs = model(inputs)
loss = criterion(outputs,classes)
_,preds = torch.max(outputs.data,1)
# statistics
running_loss += loss.data.item()
running_corrects += torch.sum(preds == classes.data)
#predictions[i:i+len(classes)] = preds.to('cpu').numpy()
#all_classes[i:i+len(classes)] = classes.to('cpu').numpy()
#all_proba[i:i+len(classes),:] = outputs.data.to('cpu').numpy()
i += len(classes)
#print('Testing: No. ', i, ' process ... total: ', size)
epoch_loss = running_loss / size
epoch_acc = running_corrects.data.item() / size
#print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss, epoch_acc))
return epoch_loss, epoch_acc
def train_model(model,dataloader,size,epochs=1,optimizer=None):
for epoch in range(epochs):
model.train()
running_loss = 0.0
running_corrects = 0
count = 0
for inputs,classes in 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
epoch_Valloss, epoch_Valacc = val_model(model,loader_valid,dset_sizes['val'])
print('epoch: ',epoch,' Loss: {:.5f} Acc: {:.5f} ValLoss: {:.5f} ValAcc: {:.5f}'.format(
epoch_loss, epoch_acc,epoch_Valloss,epoch_Valacc))
scheduler.step()
#学习率衰减
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# 模型训练
train_model(model_new,loader_train,size=dset_sizes['train'], epochs=1 ,
optimizer=optimizer)
#6.模型测试并输出csv文件
model_new.eval()
csvfile = open('csv.csv', 'w')
writer = csv.writer(csvfile)
test_root='F:/1_yan/cat_dog/cat_dog/test/'
img_test=os.listdir(test_root)
img_test.sort(key= lambda x:int(x[:-4]))
for i in range(len(img_test)):
img = Image.open(test_root+img_test[i])
img = img.convert('RGB')
input=resnet_format(img)
input=input.unsqueeze(0)
input = input.to(device)
output=model_new(input)
_,pred = torch.max(output.data,1)
print(i,pred.tolist()[0])
writer.writerow([i,pred.tolist()[0]])
csvfile.close()