pytorch入门笔记(二):数据预处理、模型的保存读取以及分类实战

torchvision.transforms常用的几个图像预处理方法
  • transforms.Resize(size) :会把图片的短边尺寸转换成size,整体长宽比不变

  • transforms.CenterCrop(size) : 在图片的中心抠出一张(size, size)的小图像,往往配合Resize使用

  • transforms.RandomRotation((n,m)) : 把图片旋转,旋转角度为n~m的随机角度,可为负数

  • transforms.RandomHorizontalFlip§ : 图片有p的概率会水平翻转

  • transforms.RandomVerticalFlip§ : 图片有p的概率会垂直翻转

  • 上面的几个对象(别看像方法,实则是创建的对象)都是对PILImage格式进行操作的,所以有两点注意事项

    • 在读取数据时,最好用PIL来读,用opencv也可以,就是容易出bug。

      from PIL import Image
      pic = Image.open(img_path)
      
    • transforms.ToTensor()要放在前面几个对象的后面

模型保存和读取

主要是三个方法,torch.save、torch.load、model.load_state_dict

最简单的存取:

torch.save(net.state_dict(),PATH)

model.load_state_dict(torch.load(PATH))

专业点的存取:

torch.save({'epoch':epoch, 'state_dict':model.state_dict(),'optimizer': optimizer.state_dict()}, PATH) #把想要的数据以map的形式记录下来

def load_checkpoint(model, checkpoint_PATH):
    model_CKPT = torch.load(checkpoint_PATH) #读取所有信息
    model.load_state_dict(model_CKPT['state_dict'])
    optimizer.load_state_dict(model_CKPT['optimizer'])
    # model_CKPT['其他信息']

只导入需要的参数:

def load_checkpoint(model, checkpoint_PATH):
    model_CKPT = torch.load(checkpoint_PATH) #读取所有信息
    pretrained_dict = model_CKPT['state_dict'] #读取预训练模型
    model_dict = model.state_dict() #读取目标模型的所有参数列表
    new_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict.keys()} #搜集预训练模型中哪些参数有用
   	model_dict.update(new_dict) #更新目标模型的参数列表(可能只更新其中一部分)
   	model.load_state_dict(model_dict) #重新load回去
预训练模型的使用

在torchvision.models中提供了很多出名的模型代码和其预训练模型,同样,网上也有很多高人写了一些牛皮的网络和预训练模型,我们在借鉴时,往往需要对模型进行改造,最常见的就是把最后一层的全链接输出改成自己想要的种类数目。

以torchvision.models的resnet34为例,我们可以读取到网络及其参数后,直接修改最后一层(前面层的数据还在),在这基础上进行几轮训练,就能达到不错的效果

net = models.resnet34(pretrained=True)
net.fc = nn.Linear(512, cfg.num_class)

学习了上面的模型导入,我们也可以自己大幅度调整网络,然后利用参数筛选更新来使用预训练模型。

训练过程中的部分参数冻结

利用name_parameters()来获取参数列表,把符合条件的参数的requires_grad设置为False,并从优化器的参数中剔除

for name, value in model.named_parameters():
    if name 满足某些条件:
        value.requires_grad = False
 
# setup optimizer
params = filter(lambda p: p.requires_grad, model.parameters()) #过滤器,requires_grad为False的过滤掉
optimizer = torch.optim.Adam(params, lr=1e-4)
学习率的调整

在训练过程中,学习率逐步衰减的效果会比较好,我们可以利用优化器的param_groups来获取当前的学习率并调整

for p in opt.param_groups:
	p['lr'] *= decay_rate
net.train()和net.eval()

在模型的训练过程中,往往会穿插校验的过程,这时就需要切换模型为训练模式或者校验模式

直接net.train()、net.eval()即可切换

在train模式下,dropout和BN层会起作用,在eval模式下,dropout不起作用,BN会直接使用之前训练好的参数。

实战:Dog Breed Identification

这是kaggle上的小比赛,我下载了它的数据集做了下实战

链接:Dog Breed Identification

数据集规模:10000张带标签的120类狗狗图像

  • 第一步:整理数据
    我从10000张图像中随机抽取500+张图像作为校验集,并把所有图片按标签重命名(方便后面获取相应的标签),按这种比例分数据集其实不是很合理,不过训练集实在是不大。

    def getDev():
        dev_path = './dev'
        train_path = './train'
        if not os.path.exists(dev_path):
            os.mkdir(dev_path)
        img_list = os.listdir(train_path)
        for i in range(len(img_list)):
            if i % 20 == 0:
                os.rename(os.path.join(train_path, img_list[i]), os.path.join(dev_path, img_list[i]))
                
    def renamePic():
        Map = {}
        with open('labels.csv', 'r') as f:
            for line in f:
                info = line.split(',')
                if info[0] == 'id':
                    continue
                Map[info[0]] = info[1].strip()
        dev_path = './dev'
        train_path = './train'
        train_list = os.listdir(train_path)
        num = 0
        for item in train_list:
            name = item.split('.')
            os.rename(os.path.join(train_path, item), os.path.join(train_path, Map[name[0]] + '+' + str(num) + '.' + name[1]))
            num += 1
        dev_list = os.listdir(dev_path)
        for item in dev_list:
            name = item.split('.')
            os.rename(os.path.join(dev_path, item), os.path.join(dev_path, Map[name[0]] + '+' + str(num) + '.' + name[1]))
            num += 1
    
  • 第二步:设计模型
    在本次实战中,采用的是torchvision.models中自带的resnet101模型进行微调(调用后预训练模型会自动下载),torchvision中不只有resnet,还有很多其他模型,详见:https://pytorch.org/docs/stable/torchvision/models.html
    参照上面讲的预训练模型使用部分,先是确认了resnet的最后一层fc层的结构,然后直接修改fc层。

    net = models.resnet101(pretrained=True)
            # net.fc = nn.Linear(2048, cfg.num_class)
            net.fc = nn.Sequential(
                nn.Linear(2048, 512),
                nn.Linear(512, cfg.num_class)
            )
    
  • 第三步:设计数据
    直接上代码:

    class MyDataset(data.Dataset):
        def __init__(self, root):
            self.root = './' + root
            self.img_list = os.listdir(self.root)
            self.Map = utils.getLabel() #获取label所对应的id
            self.transforms = transforms.Compose([
                transforms.Resize(256),
                transforms.CenterCrop(224), #resnet101预训练模型用的size(可改)
                # transforms.RandomRotation((-30, 30)), #在该分类中加数据增强反而效果变差了
                # transforms.RandomHorizontalFlip(0.5),
                transforms.ToTensor(),
                transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) #imagenet的均值方差
                # transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) #直接归一化到[-1, 1]
            ])
    
        def __getitem__(self, index):
            path = os.path.join(self.root, self.img_list[index])
            pic = Image.open(path)
            # pic = pic.resize((300, 300))
            pic = self.transforms(pic)
            label = self.Map[self.img_list[index].split('+')[0]]
            return pic, label
    
        def __len__(self):
            return len(self.img_list)
    
  • 第四步:模型训练
    本次实战中,模型训练并不把预训练好的那部分参数进行训练,换言之,只训练我们修改的fc层

    trainSet = MyDataset('train')
    devSet = MyDataset('dev')
    
    trainLoader = DataLoader(trainSet, batch_size=cfg.batch_size, shuffle=True, drop_last=True)
    devLoader = DataLoader(devSet, batch_size=1, shuffle=True, drop_last=True)
    
    #筛除不需要训练的参数
    for name, value in net.named_parameters():
    	if 'fc' not in name:
    		value.requires_grad = False
    
    params = filter(lambda p: p.requires_grad, net.parameters())
    
    opt = optim.Adam(params, lr=cfg.lr)
    
  • 总结
    对于这次实战,我进行了很多实验,具体如下:

    • size=224,标准化至(-1, 1),batch_size=32,没有数据增强,没有训练预训练部分,acc=0.86
    • size=224,imagenet标准化,batch_size=32,没有数据增强,没有训练预训练部分,acc=0.87
    • size=224,imagenet标准化,batch_size=32,没有数据增强,训练预训练部分,acc=0.81, 训练集loss很低
    • size=384,imagenet标准化,batch_size=32,没有数据增强,没有训练预训练部分,acc=0.85
    • size=224,imagenet标准化,batch_size=32,有数据增强,没有训练预训练部分,acc=0.82
    • size=224,imagenet标准化,batch_size=64,没有数据增强,没有训练预训练部分,acc=0.88

    总结了一下,对模型效果影响最大的是训不训练预训练好的参数,为什么训练预训练部分loss更低但是效果差很多呢?我想了想,预训练部分主要作用是提取图片中的特征,原本训练集是超级大的imagenet,而我们这次用的训练集不过万张,如果动了原本的参数,提取的效果肯定会变差,至于训练集的loss很低,这也是正常的,毕竟整个模型都在为了降低loss而不停修改参数,校验集的准确率却变低了很多可以理解为过拟合的现象了。第二个影响因素是数据增强,其实对于这种追求准确率,且数据都是干干净净端端正正的,确实不需要数据增强,感觉数据增强应该用在增强模型召回率上。其他部分影响不太大,但最好还是用预训练的size和均值方差。

github已上传相关代码:https://github.com/Zou-CM/pytorch_practice/tree/master/DogsClassification

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值