Pytorch学习--编程实战:猫和狗二分类

Pytorch学习系列(一)至(四)均摘自《深度学习框架PyTorch入门与实践》陈云

目录:

1.程序的主要功能

2.文件组织架构

3. 关于`__init__.py`

4.数据处理

5.模型定义

6.工具函数

7.配置文件

8.main.py

9.使用

1.程序的主要功能:

模型定义
    数据加载
    训练和测试

2.文件组织架构:

```
├── checkpoints/
├── data/
│   ├── __init__.py
│   ├── dataset.py
│   └── get_data.sh
├── models/
│   ├── __init__.py
│   ├── AlexNet.py
│   ├── BasicModule.py
│   └── ResNet34.py
└── utils/
│   ├── __init__.py
│   └── visualize.py
├── config.py
├── main.py
├── requirements.txt
├── README.md

```

其中:

- `checkpoints/`: 用于保存训练好的模型,可使程序在异常退出后仍能重新载入模型,恢复训练
- `data/`:数据相关操作,包括数据预处理、dataset实现等
- `models/`:模型定义,可以有多个模型,例如上面的AlexNet和ResNet34,一个模型对应一个文件
- `utils/`:可能用到的工具函数,在本次实验中主要是封装了可视化工具
- `config.py`:配置文件,所有可配置的变量都集中在此,并提供默认值
- `main.py`:主文件,训练和测试程序的入口,可通过不同的命令来指定不同的操作和参数
- `requirements.txt`:程序依赖的第三方库

- `README.md`:提供程序的必要说明

3. 关于`__init__.py`

可以看到,几乎每个文件夹下都有`__init__.py`,一个目录如果包含了`__init__.py` 文件,那么它就变成了一个包(package)。

`__init__.py`可以为空,也可以定义包的属性和方法,但其必须存在,其它程序才能从这个目录中导入相应的模块或函数。

例如在`data/`文件夹下有`__init__.py`,则在`main.py` 中就可以`from data.dataset import DogCat`。而如果在`__init__.py`中写入`from .dataset import DogCat`,则在main.py中就可以直接写为:`from data import DogCat`,或者`import data; dataset = data.DogCat`,相比于`from data.dataset import DogCat`更加便捷。

4.数据处理

数据的相关处理主要保存在`data/dataset.py`中。

关于数据加载的相关操作,其基本原理就是使用`Dataset`提供数据集的封装,再使用`Dataloader`实现数据并行加载。

Kaggle提供的数据包括训练集和测试集,而我们在实际使用中,还需专门从训练集中取出一部分作为验证集。对于这三类数据集,其相应操作也不太一样,而如果专门写三个`Dataset`,则稍显复杂和冗余,因此这里通过加一些判断来区分。对于训练集,我们希望做一些数据增强处理,如随机裁剪、随机翻转、加噪声等,而验证集和测试集则不需要。下面看`dataset.py`的代码:

#coding:utf8
    import os
    from PIL import  Image
    from torch.utils import data
    import numpy as np
    from torchvision import  transforms as T
     
     
    class DogCat(data.Dataset):
        
        def __init__(self,root,transforms=None,train=True,test=False):
            ‘‘‘
            主要目标: 获取所有图片的地址,并根据训练,验证,测试划分数据
            ‘‘‘
            self.test = test
            imgs = [os.path.join(root,img) for img in os.listdir(root)]
     
            # test1: data/test1/8973.jpg
            # train: data/train/cat.10004.jpg
            if self.test:
                imgs = sorted(imgs,key=lambda x:int(x.split(‘.‘)[-2].split(‘/‘)[-1]))
            else:
                imgs = sorted(imgs,key=lambda x:int(x.split(‘.‘)[-2]))
                
            imgs_num = len(imgs)
     
            if self.test:
                self.imgs = imgs
            elif train:
                self.imgs = imgs[:int(0.7*imgs_num)]
            else :
                self.imgs = imgs[int(0.7*imgs_num):]
                
        
            if transforms is None:
                normalize = T.Normalize(mean = [0.485, 0.456, 0.406],
                                         std = [0.229, 0.224, 0.225])
     
                if self.test or not train:
                    self.transforms = T.Compose([
                        T.Scale(224),
                        T.CenterCrop(224),
                        T.ToTensor(),
                        normalize
                        ])
                else :
                    self.transforms = T.Compose([
                        T.Scale(256),
                        T.RandomSizedCrop(224),
                        T.RandomHorizontalFlip(),
                        T.ToTensor(),
                        normalize
                        ])
                    
            
        def __getitem__(self,index):
            ‘‘‘
            一次返回一张图片的数据
            ‘‘‘
            img_path = self.imgs[index]
            if self.test: label = int(self.imgs[index].split(‘.‘)[-2].split(‘/‘)[-1])
            else: label = 1 if ‘dog‘ in img_path.split(‘/‘)[-1] else 0
            data = Image.open(img_path)
            data = self.transforms(data)
            return data, label
        
        def __len__(self):
            return len(self.imgs)

5.模型定义

模型的定义主要保存在`models/`目录下,其中`BasicModule`是对`nn.Module`的简易封装,提供快速加载和保存模型的接口。

#coding:utf8
    import torch as t
    import time
     
     
    class BasicModule(t.nn.Module):
        ‘‘‘
        封装了nn.Module,主要是提供了save和load两个方法
        ‘‘‘
     
        def __init__(self):
            super(BasicModule,self).__init__()
            self.model_name=str(type(self))# 默认名字
     
        def load(self, path):
            ‘‘‘
            可加载指定路径的模型
            ‘‘‘
            self.load_state_dict(t.load(path))
     
        def save(self, name=None):
            ‘‘‘
            保存模型,默认使用“模型名字+时间”作为文件名
            ‘‘‘
            if name is None:
                prefix = ‘checkpoints/‘ + self.model_name + ‘_‘
                name = time.strftime(prefix + ‘%m%d_%H:%M:%S.pth‘)
            t.save(self.state_dict(), name)
            return name
     
     
    class Flat(t.nn.Module):
        ‘‘‘
        把输入reshape成(batch_size,dim_length)
        ‘‘‘
     
        def __init__(self):
            super(Flat, self).__init__()
            #self.size = size
     
        def forward(self, x):
            return x.view(x.size(0), -1)

6.工具函数

在项目中,我们可能会用到一些helper方法,这些方法可以统一放在`utils/`文件夹下,需要使用时再引入。在本例中主要是封装了可视化工具visdom的一些操作,其代码如下,在本次实验中只会用到`plot`方法,用来统计损失信息。

#coding:utf8
    import visdom
    import time
    import numpy as np
     
    class Visualizer(object):
        ‘‘‘
        封装了visdom的基本操作,但是你仍然可以通过`self.vis.function`
        调用原生的visdom接口
        ‘‘‘
     
        def __init__(self, env=‘default‘, **kwargs):
            self.vis = visdom.Visdom(env=env, **kwargs)
            
            # 画的第几个数,相当于横座标
            # 保存(’loss‘,23) 即loss的第23个点
            self.index = {}
            self.log_text = ‘‘
        def reinit(self,env=‘default‘,**kwargs):
            ‘‘‘
            修改visdom的配置
            ‘‘‘
            self.vis = visdom.Visdom(env=env,**kwargs)
            return self
     
        def plot_many(self, d):
            ‘‘‘
            一次plot多个
            @params d: dict (name,value) i.e. (‘loss‘,0.11)
            ‘‘‘
            for k, v in d.items():
                self.plot(k, v)
     
        def img_many(self, d):
            for k, v in d.items():
                self.img(k, v)
     
        def plot(self, name, y,**kwargs):
            ‘‘‘
            self.plot(‘loss‘,1.00)
            ‘‘‘
            x = self.index.get(name, 0)
            self.vis.line(Y=np.array([y]), X=np.array([x]),
                          win=name,
                          opts=dict(title=name),
                          update=None if x == 0 else ‘append‘,
                          **kwargs
                          )
            self.index[name] = x + 1
     
        def img(self, name, img_,**kwargs):
            ‘‘‘
            self.img(‘input_img‘,t.Tensor(64,64))
            self.img(‘input_imgs‘,t.Tensor(3,64,64))
            self.img(‘input_imgs‘,t.Tensor(100,1,64,64))
            self.img(‘input_imgs‘,t.Tensor(100,3,64,64),nrows=10)
            !!!don‘t ~~self.img(‘input_imgs‘,t.Tensor(100,64,64),nrows=10)~~!!!
            ‘‘‘
            self.vis.images(img_.cpu().numpy(),
                           win=name,
                           opts=dict(title=name),
                           **kwargs
                           )
     
     
        def log(self,info,win=‘log_text‘):
            ‘‘‘
            self.log({‘loss‘:1,‘lr‘:0.0001})
            ‘‘‘
     
            self.log_text += (‘[{time}] {info} <br>‘.format(
                                time=time.strftime(‘%m%d_%H%M%S‘),\
                                info=info))
            self.vis.text(self.log_text,win)   
     
        def __getattr__(self, name):
            return getattr(self.vis, name)

7.配置文件

在模型定义、数据处理和训练等过程都有很多变量,这些变量应提供默认值,并统一放置在配置文件中,这样在后期调试、修改代码或迁移程序时会比较方便,在这里我们将所有可配置项放在`config.py`中。

#coding:utf8
    import warnings
    class DefaultConfig(object):
        env = ‘default‘ # visdom 环境
        model = ‘ResNet34‘ # 使用的模型,名字必须与models/__init__.py中的名字一致
        
        train_data_root = ‘./data/train/‘ # 训练集存放路径
        test_data_root = ‘./data/test1‘ # 测试集存放路径
        load_model_path = ‘checkpoints/model.pth‘ # 加载预训练的模型的路径,为None代表不加载
     
        batch_size = 128 # batch size
        use_gpu = True # user GPU or not
        num_workers = 4 # how many workers for loading data
        print_freq = 20 # print info every N batch
     
        debug_file = ‘/tmp/debug‘ # if os.path.exists(debug_file): enter ipdb
        result_file = ‘result.csv‘
          
        max_epoch = 10
        lr = 0.1 # initial learning rate
        lr_decay = 0.95 # when val_loss increase, lr = lr*lr_decay
        weight_decay = 1e-4 # 损失函数
     
     
     
    def parse(self,kwargs):
            ‘‘‘
            根据字典kwargs 更新 config参数
            ‘‘‘
            for k,v in kwargs.items():
                if not hasattr(self,k):
                    warnings.warn("Warning: opt has not attribut %s" %k)
                setattr(self,k,v)
     
            print(‘user config:‘)
            for k,v in self.__class__.__dict__.items():
                if not k.startswith(‘__‘):
                    print(k,getattr(self,k))
     
     
    DefaultConfig.parse = parse
    opt =DefaultConfig()
    # opt.parse = parse

8.main.py

在讲解主程序`main.py`之前,我们先来看看2017年3月谷歌开源的一个命令行工具`fire`[^3] ,通过`pip install fire`即可安装。下面来看看`fire`的基础用法,假设`example.py`文件内容如下:

import fire
     
    def add(x, y):
      return x + y
      
    def mul(**kwargs):
        a = kwargs[‘a‘]
        b = kwargs[‘b‘]
        return a * b
     
    if __name__ == ‘__main__‘:
      fire.Fire()

python example.py add 1 2 # 执行add(1, 2)
    python example.py mul --a=1 --b=2 # 执行mul(a=1, b=2), kwargs={‘a‘:1, ‘b‘:2}
    python example.py add --x=1 --y==2 # 执行add(x=1, y=2)

在主程序`main.py`中,主要包含四个函数,其中三个需要命令行执行,`main.py`的代码组织结构如下:

def train(**kwargs):
        ‘‘‘
        训练
        ‘‘‘
        pass
        
    def val(model, dataloader):
        ‘‘‘
        计算模型在验证集上的准确率等信息,用以辅助训练
        ‘‘‘
        pass
     
    def test(**kwargs):
        ‘‘‘
        测试(inference)
        ‘‘‘
        pass
     
    def help():
        ‘‘‘
        打印帮助的信息
        ‘‘‘
        print(‘help‘)
     
    if __name__==‘__main__‘:
        import fire
        fire.Fire()

训练

训练的主要步骤如下:

- 定义网络
- 定义数据
- 定义损失函数和优化器
- 计算重要指标
- 开始训练
  - 训练网络
  - 可视化各种指标
  - 计算在验证集上的指标

def train(**kwargs):
        opt.parse(kwargs)
        vis = Visualizer(opt.env)
     
        # step1: configure model
        model = getattr(models, opt.model)()
        if opt.load_model_path:
            model.load(opt.load_model_path)
        if opt.use_gpu: model.cuda()
     
        # step2: data
        train_data = DogCat(opt.train_data_root,train=True)
        val_data = DogCat(opt.train_data_root,train=False)
        train_dataloader = DataLoader(train_data,opt.batch_size,
                            shuffle=True,num_workers=opt.num_workers)
        val_dataloader = DataLoader(val_data,opt.batch_size,
                            shuffle=False,num_workers=opt.num_workers)
        
        # step3: criterion and optimizer
        criterion = t.nn.CrossEntropyLoss()
        lr = opt.lr
        optimizer = t.optim.Adam(model.parameters(),lr = lr,weight_decay = opt.weight_decay)
            
        # step4: meters
        loss_meter = meter.AverageValueMeter()
        confusion_matrix = meter.ConfusionMeter(2)
        previous_loss = 1e100
     
        # train
        for epoch in range(opt.max_epoch):
            
            loss_meter.reset()
            confusion_matrix.reset()
     
            for ii,(data,label) in enumerate(train_dataloader):
     
                # train model
                input = Variable(data)
                target = Variable(label)
                if opt.use_gpu:
                    input = input.cuda()
                    target = target.cuda()
     
                optimizer.zero_grad()
                score = model(input)
                loss = criterion(score,target)
                loss.backward()
                optimizer.step()
                
                
                # meters update and visualize
                loss_meter.add(loss.data[0])
                confusion_matrix.add(score.data, target.data)
     
                if ii%opt.print_freq==opt.print_freq-1:
                    vis.plot(‘loss‘, loss_meter.value()[0])
                    
                    # 进入debug模式
                    if os.path.exists(opt.debug_file):
                        import ipdb;
                        ipdb.set_trace()
     
     
            model.save()
     
            # validate and visualize
            val_cm,val_accuracy = val(model,val_dataloader)
     
            vis.plot(‘val_accuracy‘,val_accuracy)
            vis.log("epoch:{epoch},lr:{lr},loss:{loss},train_cm:{train_cm},val_cm:{val_cm}".format(
                        epoch = epoch,loss = loss_meter.value()[0],val_cm = str(val_cm.value()),train_cm=str(confusion_matrix.value()),lr=lr))
            
            # update learning rate
            if loss_meter.value()[0] > previous_loss:          
                lr = lr * opt.lr_decay
                # 第二种降低学习率的方法:不会有moment等信息的丢失
                for param_group in optimizer.param_groups:
                    param_group[‘lr‘] = lr
            
     
            previous_loss = loss_meter.value()[0]

验证

验证相对来说比较简单,但要注意需将模型置于验证模式(`model.eval()`),验证完成后还需要将其置回为训练模式(`model.train()`),这两句代码会影响`BatchNorm`和`Dropout`等层的运行模式。验证模型准确率的代码如下。

def val(model,dataloader):
        ‘‘‘
        计算模型在验证集上的准确率等信息
        ‘‘‘
        model.eval()
        confusion_matrix = meter.ConfusionMeter(2)
        for ii, data in enumerate(dataloader):
            input, label = data
            val_input = Variable(input, volatile=True)
            val_label = Variable(label.type(t.LongTensor), volatile=True)
            if opt.use_gpu:
                val_input = val_input.cuda()
                val_label = val_label.cuda()
            score = model(val_input)
            confusion_matrix.add(score.data.squeeze(), label.type(t.LongTensor))
     
        model.train()
        cm_value = confusion_matrix.value()
        accuracy = 100. * (cm_value[0][0] + cm_value[1][1]) / (cm_value.sum())
        return confusion_matrix, accuracy

测试

测试时,需要计算每个样本属于狗的概率,并将结果保存成csv文件。测试的代码与验证比较相似,但需要自己加载模型和数据。

def test(**kwargs):
        opt.parse(kwargs)
        import ipdb;
        ipdb.set_trace()
        # configure model
        model = getattr(models, opt.model)().eval()
        if opt.load_model_path:
            model.load(opt.load_model_path)
        if opt.use_gpu: model.cuda()
     
        # data
        train_data = DogCat(opt.test_data_root,test=True)
        test_dataloader = DataLoader(train_data,batch_size=opt.batch_size,shuffle=False,num_workers=opt.num_workers)
        results = []
        for ii,(data,path) in enumerate(test_dataloader):
            input = t.autograd.Variable(data,volatile = True)
            if opt.use_gpu: input = input.cuda()
            score = model(input)
            probability = t.nn.functional.softmax(score)[:,0].data.tolist()
            # label = score.max(dim = 1)[1].data.tolist()
            
            batch_results = [(path_,probability_) for path_,probability_ in zip(path,probability) ]
     
            results += batch_results
        write_csv(results,opt.result_file)
     
        return results

帮助函数

为了方便他人使用, 程序中还应当提供一个帮助函数,用于说明函数是如何使用。程序的命令行接口中有众多参数,如果手动用字符串表示不仅复杂,而且后期修改config文件时,还需要修改对应的帮助信息,十分不便。这里使用了Python标准库中的inspect方法,可以自动获取config的源代码。help的代码如下:

def help():
        ‘‘‘
        打印帮助的信息: python file.py help
        ‘‘‘
        
        print(‘‘‘
        usage : python file.py <function> [--args=value]
        <function> := train | test | help
        example:
                python {0} train --env=‘env0701‘ --lr=0.01
                python {0} test --dataset=‘path/to/dataset/root/‘
                python {0} help
        avaiable args:‘‘‘.format(__file__))
     
        from inspect import getsource
        source = (getsource(opt.__class__))
        print(source)

9.使用

正如`help`函数的打印信息所述,可以通过命令行参数指定变量名.下面是三个使用例子,fire会将包含`-`的命令行参数自动转层下划线`_`,也会将非数值的值转成字符串。所以`--train-data-root=data/train`和`--train_data_root=‘data/train‘`是等价的。

```
# 训练模型
python main.py train
        --train-data-root=data/train/
        --load-model-path=‘checkpoints/resnet34_16:53:00.pth‘
        --lr=0.005
        --batch-size=32
        --model=‘ResNet34‘  
        --max-epoch = 20

# 测试模型
python main.py test
       --test-data-root=data/test1
       --load-model-path=‘checkpoints/resnet34_00:23:05.pth‘
       --batch-size=128
       --model=‘ResNet34‘
       --num-workers=12

# 打印帮助信息
python main.py help
---------------------  
作者:寻找如意  
来源:CSDN  
原文:https://blog.csdn.net/qq_34447388/article/details/79541824  
版权声明:本文为博主原创文章,转载请附上博文链接!

原文地址:https://www.cnblogs.com/shuimuqingyang/p/10266097.html

时间: 01-11

Pytorch学习--编程实战:猫和狗二分类的相关文章

JAVA并发编程实战 读书笔记(二)对象的共享

<java并发编程实战>读书摘要 birdhack 2015年1月2日 对象的共享 JAVA并发编程实战读书笔记 我们已经知道了同步代码块和同步方法可以确保以原子的方式执行操作,但一种常见的误解是,认为关键之synchronized只能用于实现原子性或者确定临界区.同步还有另一个重要的方面:内存可见性. 1.可见性 为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 在没有同步的情况下,编译器.处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整.在缺乏足够同步的多线程程

《Java并发编程实战》第十二章 测试并发程序 读书笔记

并发测试分为两类:安全性测试(无论错误的行为不会发生)而活性测试(会发生). 安全測试 - 通常採用測试不变性条件的形式,即推断某个类的行为是否与其它规范保持一致. 活跃性測试 - 包含进展測试和无进展測试两个方面. 性能測试与活跃性測试相关,主要包含:吞吐量.响应性.可伸缩性. 一.正确性測试 找出须要检查的不变条件和后延条件. import java.util.concurrent.Semaphore; public class BoundedBuffer<E> { private fin

W3cschool编程实战学习HTML5&amp;CSS实战简介《一》

上周感兴趣想学习一下HTML基础入门.(虽然可能和自己就业没多大关系,但是多了解一点总没坏处.)我在W3cshool上学习编程实战学习中HTML5&CSS的课程,由于平时比较忙,我只能每天晚上抽半个小时做题,本周末又恰逢研究生考试现场确认,因此,目前只写了前面一部分.后续会继续更新. html简单示例分析 <!DOCTYPE html> <html> <body> <h1>我是第一个标题</h1> <p>我是第一个段落.<

java并发编程实战学习(3)--基础构建模块

转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到空间可用:如果队列为空,那么take方法将阻塞直到有元素可用.队列可以是有界的也可以是无界的. 如果生产者生成工作的速率比消费者处理工作的速率款,那么工作项会在队列中累计起来,最终好紧内存.同样,put方法的阻塞特性也极大地简化了生产者的编码.如果使用有界队列,当队列充满时,生产者将阻

[笔记][Java7并发编程实战手册]2.5使用Lock实现同步二

[笔记][Java7并发编程实战手册]系列目录 概要 接上一篇文章,练习修改锁的公平性,和在所中使用条件. 修改锁的公平性ReentrantLock /** *构造一个锁对象,默认为非公平锁 */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 根据ReentrantLock的构造可以看出来,默认会构造非公平锁: 公平锁与非公平锁有什么区别 公平锁 :有多个线程并发访

大数据Spark蘑菇云前传第15课:Scala类型参数编程实战及Spark源码鉴赏(学习笔记)

前传第15课:Scala类型参数编程实战及Spark源码鉴赏 本課課程: Spark源码中的Scala类型系統的使用 Scala类型系統编程操作实战 Spark源码中的Scala类型系統的使用 classOf[RDD[_]] 這個也是类型系統 這里的意思是說 B 這種類型必需至少是 A 這樣類型 Ordering Scala类型系統编程操作实战 作為類型系統最大的就可以對類型進行限制,在Scala 中的類型系統,他本身也作為對象.e.g. 我們可以建立 Person 這個類,現在可以建立一個什麼

【火炉炼AI】深度学习005-简单几行Keras代码解决二分类问题

[火炉炼AI]深度学习005-简单几行Keras代码解决二分类问题 (本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0) 很多文章和教材都是用MNIST数据集作为深度学习届的"Hello World"程序,但是这个数据集有一个很大的特点:它是一个典型的多分类问题(一共有10个分类),在我们刚刚开始接触深度学习时,我倒是觉得

MapReduce编程实战之“高级特性”

本篇介绍MapReduce的一些高级特性,如计数器.数据集的排序和连接.计数器是一种收集作业统计信息的有效手段,排序是MapReduce的核心技术,MapReduce也能够执行大型数据集间的""连接(join)操作. 计数器 计数器是一种收集作业统计信息的有效手段,用于质量控制或应用级统计.计数器还可用于辅助诊断系统故障.对于大型分布式系统来说,获取计数器比分析日志文件容易的多. 示例一:气温缺失及不规则数据计数器 import java.io.IOException; import

《R实战》读书笔记二

第一章 R简介 本章概要 1安装R 2理解R语言 3运行R程序 本章所介绍的内容概括如下. 一个典型的数据分析步骤如图1所示. 图1:典型数据分析步骤 简而言之,现今的数据分析要求我们从多种数据源中获取数据.数据合并.标注.清洗和分析,并且把分析的结果进行展示,形成报告或者系统,辅助决策.R能够满足现今数据分析的要求. 为什么用R? R是一个适合统计分析和绘图的环境与语言.它是开源.免费的,获得世界范围社区支持.统计分析和绘图工具已经很多了,例如:SPSS,SAS,Excel,Stata和Min