PyTorch--常用的工具

PyTorch–常用的工具

在训练神经网络的过程中需要用到很多工具,其中最重要的三部分是数据,可视化和GPU加速

一.数据处理

数据的处理对训练神经网络来说十分重要,良好的数据处理不仅加速模型训练,也会提高模型效果

数据加载

在PyTorch中,数据加载可通过自定义的数据集对象实现。数据集对象被抽象为Dataset类,实现自定义的数据集需要继承Dataset,并实现两个python魔法方法:

  • getitemgetitem**(index)
  • lenlen**()
import torch as t
from torch.utils import data
import os
from PIL import Image
import numpy as np

class DogCat(data.Dataset):
    def __init__(self,root):
        imgs=os.listdir(root)
        # 所有图片的绝对路径
        # 这里不实际加载图片,只是指定路径,当调用__getitem__时才会真正读图片
        self.imgs=[os.path.join(root,img) for img in imgs]
        
    def __getitem__(self,index):
        img_path=self.imgs[index]
        # dog->1,cat->0
        label=1 if 'dog' in img_path.split('/')[-1] else 0
        pil_img=Image.open(img_path)
        array=np.asarray(pil_img)
        data=t.from_numpy(array)
        return data,label
    
    def __len__(self):
        return len(self.imgs)
dataset=DogCat('./data/dogcat/')
img,label=dataset[0]   # 相当于调用dataset.__getitem__(0)
for img,label in dataset:
    print(img.size(),img.float().mean(),label)
torch.Size([500, 497, 3]) tensor(106.4915) 0
torch.Size([499, 379, 3]) tensor(171.8085) 0
torch.Size([236, 289, 3]) tensor(130.3004) 0
torch.Size([374, 499, 3]) tensor(115.5177) 0
torch.Size([375, 499, 3]) tensor(116.8139) 1
torch.Size([375, 499, 3]) tensor(150.5080) 1
torch.Size([377, 499, 3]) tensor(151.7174) 1
torch.Size([400, 300, 3]) tensor(128.1550) 1

通过上面的代码,我们学习了如何自定义自己的数据集,并可以,依次获取。但这里,返回的数据不适合实际使用,因其具有如下两方面问题:

  • 返回样本的形状不一,每张图片的大小不一样,这对于需要取batch训练的神经网络来说很不友好
  • 返回样本的数值较大,未归一化至[-1,1]

针对上述问题,PyTorch提供了torchvision。它是一个视觉工具包,提供了很多视觉图像处理的工具,其中transforms模块提供了对PIL Image对象和Tensor对象的常用操作

对PIL Image的常见操作如下:

  • Scale:调整图片尺寸,长宽比保持不变
  • CenterCrop,RandomGrop,RandomSizedGrop:裁剪图片
  • Pad:填充
  • ToTensor:将PIL Image对象转成Tensor,会自动将[0,255]归一化至[0,1]

    对Tensor的常见操作如下:
  • Normalize:标准化,即减均值,除以标准差
  • ToPILImage:将Tensor转为PIL Image对象

下面我们就用transforms的这些操作来优化上面实现的dataset

import os
from PIL import Image
import numpy as np
from torchvision import transforms as T

transform=T.Compose([
    # 缩放图片(Image),保持长宽比不变,最短边为224像素
    T.Scale(224),
    # 从图片中间切出224*224的图片
    T.CenterCrop(224),
    # 将图片(Image)转成Tensor,归一化至[0,1]
    T.ToTensor(),
    # 标准化至[-1,1],规定均值和标准差
    T.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5])
])

class DogCat(data.Dataset):
    def __init__(self,root,transforms=None):
        imgs=os.listdir(root)
        # 所有图片的绝对路径
        # 这里不实际加载图片,只是指定路径,当调用__getitem__时才会真正读图片
        self.imgs=[os.path.join(root,img) for img in imgs]
        self.transforms=transforms
        
    def __getitem__(self,index):
        img_path=self.imgs[index]
        label=0 if 'dog' in img_path.split('/')[-1] else 1
        data=Image.open(img_path)
        if self.transforms:
            data=self.transforms(data)
        return data,label
    
    def __len__(self):
        return len(self.imgs)
    
dataset=DogCat('./data/dogcat/',transforms=transform)
img,label=dataset[0]   # 相当于调用dataset.__getitem__(0)
for img,label in dataset:
    print(img.size(),img.float().mean(),label)
E:\Anaconda\lib\site-packages\torchvision\transforms\transforms.py:207: UserWarning: The use of the transforms.Scale transform is deprecated, please use transforms.Resize instead.
  warnings.warn("The use of the transforms.Scale transform is deprecated, " +


torch.Size([3, 224, 224]) tensor(-0.1654) 1
torch.Size([3, 224, 224]) tensor(0.3892) 1
torch.Size([3, 224, 224]) tensor(0.0711) 1
torch.Size([3, 224, 224]) tensor(-0.0462) 1
torch.Size([3, 224, 224]) tensor(-0.0649) 0
torch.Size([3, 224, 224]) tensor(0.1176) 0
torch.Size([3, 224, 224]) tensor(0.2234) 0
torch.Size([3, 224, 224]) tensor(-0.0267) 0

除了上述操作之外,transforms还可以通过lambda封装自定义的转换策略。例如,想对PIL Image进行随机旋转,则可写成trans=T.Lambda(lambda img:img.rotate(random()*360))

torchvision已经预先实现了常用的Dataset,包括前面使用过的CIFAR-10,以及ImageNet,COCO,MNIST,LSUN等数据集,可通过调用torchvision.datasets下相应对象来调用相关数据集

ImageFolder假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名,其构造函数如下:

ImageFolder(root,transform=None,target_transform=None,loader=default_loader)

它主要有以下四个参数:

  • root:在root指定的路径下寻找图片
  • transform:对PIL Image进行转换操作,transform的输入是使用loader读取图片的返回对象
  • target_transform:对label的转换
  • loader:指定加载图片的函数,默认操作是读取为PIL Image对象

label是按照文件夹名顺序后存成字典的,即{类名:类序号(从0开始)},一般来说最好直接将文件夹命名为从0开始的数字,这样会和ImageFolder实际的label一致,如果不是这种命名规范,建议通过self.class_to_idx属性了解label和文件夹名的映射关系

from torchvision.datasets import ImageFolder
dataset=ImageFolder("data/dogcat_2/")
# cat文件夹的图片对应label 0,dog对应1
dataset.class_to_idx
{'cat': 0, 'dog': 1}
# 所有图片的路径和对应的label
dataset.imgs
[('data/dogcat_2/cat\\cat.12484.jpg', 0),
 ('data/dogcat_2/cat\\cat.12485.jpg', 0),
 ('data/dogcat_2/cat\\cat.12486.jpg', 0),
 ('data/dogcat_2/cat\\cat.12487.jpg', 0),
 ('data/dogcat_2/dog\\dog.12496.jpg', 1),
 ('data/dogcat_2/dog\\dog.12497.jpg', 1),
 ('data/dogcat_2/dog\\dog.12498.jpg', 1),
 ('data/dogcat_2/dog\\dog.12499.jpg', 1)]
# 没有任何的transform,所以返回的还是PIL Image对象
# 第一维是第几张图,第二维为1返回label
dataset[0][1]
# 为0返回图片数据,返回的Image对象
dataset[0][0]

output_22_0.png

from torchvision import transforms as T
# 加上transform
normalize=T.Normalize(mean=[0.4,0.4,0.4],std=[0.2,0.2,0.2])

transform=T.Compose([
    T.RandomSizedCrop(224),
    T.RandomHorizontalFlip(),
    T.ToTensor(),
    normalize,
])
dataset=ImageFolder("data/dogcat_2/",transform=transform)
# 深度学习中图片数据一般保存成CxHxW,即通道数x图片高x图片宽
dataset[0][0].size()
torch.Size([3, 224, 224])
to_img=T.ToPILImage()
# 0.2和0.4是标准差和均值的近似
to_img(dataset[0][0]*0.2+0.4)

output_26_0.png

Dataset只负责数据的抽象,一次调用getitem只返回一个样本。前面提到过,在训练神经网络时,是对一个batch的数据操作,同时还需要对数据进行shuffle和并行加速。对此,PyTorch提供了DataLoader帮助我们实现这些功能

DataLoader的函数定义如下:

DataLoader(dataset,batch_size=1,shuffle=False,smapler=None,num_workers=0,collate_fn=default_collate,pin_memory=False,drop_last=False)
  • dataset:加载的数据集
  • batch_size:batch size(批大小)
  • shuffle:是否将数据打乱
  • sampler:样本抽样
  • num_workers:使用多进程加载的进程数,0代表不使用多进程
  • collate_fn:如何将多个样本数据拼接成一个batch,一般使用默认的拼接方式即可
  • pin_memory:是否将数据保存在pin memory区,pin memory中的数据转到GPU会快一些
  • drop_last:dataset中的数据个数可能不是batch_size的整数倍,drop_last为True会将多出来不足一个batch的数据丢弃
from torch.utils.data import DataLoader
dataloader=DataLoader(dataset,batch_size=3,shuffle=True,num_workers=0,drop_last=False)

dataiter=iter(dataloader)
imgs,labels=next(dataiter)
# batch_size,channel,height,weight
imgs.size()
torch.Size([3, 3, 224, 224])

dataloader是一个可迭代的对象,我们可以像使用迭代器一样使用它,例如:

for batch_datas,batch_labels in dataloader:
    train()
    
# 或
dataiter=iter(dataloader)
batch_datas,batch_labelsl=next(dataiter)

在数据处理中,有时会出现某个样本无法读取等问题。这时在getitem函数中将出现异常,此时最好的解决方案即是将出错的样本剔除。如果遇到这种情况实在无法处理,则可以返回None对象,然后在DataLoader中实现自定义的collate_fn,将空对象过滤掉。但要注意,在这种情况下dataloader返回的一个batch的样本数目会少于batch_size

class NewDogCat(DogCat):
    def __getitem__(self,index):
        try:
            # 调用父类的获取函数,即DogCat.__getitem__(self,index)
            return super(NewDogCat,self).__getitem__(index)
        except:
            return None,None

# 导入默认的拼接方式
from torch.utils.data.dataloader import default_collate

def my_collate_fn(batch):
    """
    batch中的每个元素形如(data,label)
    """
    # 过滤为None的数据
    batch=list(filter(lambda x:x[0] is not None,batch))
    return default_collate(batch)
dataset=NewDogCat("data/dogcat_wrong/",transforms=transform)
dataset[5]
(tensor([[[-0.5098, -0.8824, -1.1176,  ...,  1.8431,  1.4902,  0.4314],
          [-0.6078, -0.9412, -1.0196,  ...,  1.8431,  1.5098,  0.4706],
          [-0.7647, -1.0196, -0.9608,  ...,  1.8235,  1.5294,  0.4902],
          ...,
          [ 2.9216,  2.9608,  2.9020,  ...,  1.0588,  1.0588,  1.0588],
          [ 2.8824,  2.9216,  2.8431,  ...,  1.4118,  1.3725,  1.3725],
          [ 2.8431,  2.8824,  2.8039,  ...,  1.5294,  1.5294,  1.5490]],
 
         [[-0.0392, -0.4314, -0.6275,  ...,  1.4706,  1.1176,  0.1373],
          [-0.1373, -0.4902, -0.5294,  ...,  1.4706,  1.1569,  0.1569],
          [-0.2941, -0.5686, -0.5098,  ...,  1.4902,  1.1765,  0.1765],
          ...,
          [ 2.9216,  2.9608,  2.9020,  ...,  1.3725,  1.3529,  1.3529],
          [ 2.9216,  3.0000,  2.9216,  ...,  1.6667,  1.6078,  1.6078],
          [ 2.9020,  2.9804,  2.9216,  ...,  1.7843,  1.7843,  1.8039]],
 
         [[ 0.8824,  0.5294,  0.3529,  ...,  0.4902,  0.1373, -0.7647],
          [ 0.7843,  0.4706,  0.4314,  ...,  0.5098,  0.1961, -0.7255],
          [ 0.6078,  0.3922,  0.4706,  ...,  0.5098,  0.2157, -0.6863],
          ...,
          [ 2.9216,  2.9608,  2.9020,  ...,  1.0196,  1.0392,  1.0784],
          [ 2.9020,  2.9804,  2.9020,  ...,  1.4706,  1.4118,  1.4118],
          [ 2.8824,  2.9608,  2.8824,  ...,  1.5882,  1.5882,  1.6078]]]), 0)
dataloader=DataLoader(dataset,2,collate_fn=my_collate_fn,num_workers=1)
for batch_datas,batch_labels in dataloader:
    print(batch_datas.size(),batch_labels.size())

二.视觉工具包:torchvision

torchvision主要包括以下三部分:

  • models:提供深度学习中各种经典网络的网络结构及预训练好的模型,包括AlexNet,VGG系列,ResNet系列,Inception系列等
  • datasets:提供常用的数据集加载,设计上都是继承torch.utils.data.Dataset,主要包括MNIST,CIFAR10/100,ImageNet,COCO等
  • transforms:提供常用的数据预处理操作,主要包括对Tensor及PIL Image对象的操作
from torchvision import models
from torch import nn

# 加载预训练好的模型,如果不存在会下载
# 预训练好的模型保存在~/.torch/models/下面
resnet34=models.resnet34(pretrained=True,num_classes=1000)

# 修改最后的全连接层为10分类问题(默认是ImageNet上的1000分类)
resnet34.fc=nn.Linear(512,10)
from torchvision import datasets

# 指定数据集路径为data,如果数据集不存在则进行下载
# 通过train=False获取测试集
dataset=datasets.MNIST("data/",download=True,train=False,transform=transform)
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to data/MNIST\raw\train-images-idx3-ubyte.gz


100%|█████████▉| 9871360/9912422 [00:13<00:00, 1188747.47it/s]

Extracting data/MNIST\raw\train-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to data/MNIST\raw\train-labels-idx1-ubyte.gz



0it [00:00, ?it/s]
  0%|          | 0/28881 [00:00<?, ?it/s]
 57%|█████▋    | 16384/28881 [00:00<00:00, 59945.44it/s]
32768it [00:00, 38427.66it/s]                           

Extracting data/MNIST\raw\train-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to data/MNIST\raw\t10k-images-idx3-ubyte.gz



0it [00:00, ?it/s]
  0%|          | 0/1648877 [00:00<?, ?it/s]
  1%|          | 16384/1648877 [00:00<00:26, 62465.55it/s]
  3%|▎         | 49152/1648877 [00:01<00:21, 74382.81it/s]
  6%|▌         | 98304/1648877 [00:01<00:16, 92296.04it/s]
 10%|▉         | 163840/1648877 [00:01<00:12, 115207.62it/s]
 14%|█▍        | 229376/1648877 [00:01<00:10, 138903.86it/s]
 24%|██▍       | 393216/1648877 [00:01<00:06, 182372.38it/s]
 26%|██▋       | 434176/1648877 [00:02<00:07, 172328.72it/s]
 41%|████      | 679936/1648877 [00:02<00:04, 231085.39it/s]
 48%|████▊     | 794624/1648877 [00:02<00:03, 273772.31it/s]
 56%|█████▌    | 917504/1648877 [00:02<00:02, 320331.10it/s]
 64%|██████▎   | 1048576/1648877 [00:03<00:01, 410840.31it/s]
 68%|██████▊   | 1122304/1648877 [00:03<00:01, 408075.09it/s]
 73%|███████▎  | 1196032/1648877 [00:03<00:01, 393825.71it/s]
 81%|████████  | 1335296/1648877 [00:03<00:00, 435269.59it/s]
 90%|████████▉ | 1482752/1648877 [00:03<00:00, 475917.06it/s]
 99%|█████████▉| 1630208/1648877 [00:04<00:00, 560651.29it/s]
1654784it [00:04, 395803.27it/s]                             

Extracting data/MNIST\raw\t10k-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to data/MNIST\raw\t10k-labels-idx1-ubyte.gz



0it [00:00, ?it/s]
  0%|          | 0/4542 [00:00<?, ?it/s]
8192it [00:00, 16627.37it/s]            

Extracting data/MNIST\raw\t10k-labels-idx1-ubyte.gz
Processing...
Done!


9920512it [00:30, 1188747.47it/s]

Transform中涵盖了大部分对Tensor和PIL Image的常用处理。需要注意的是转换分为两步,第一步:构建转换操作,例如transform=transforms.Normalize(mean=x,std=y);第二步:执行转换操作,例如ouput=transf(input).另外还可将多个处理操作用Compose拼接起来,构成一个处理转换流程

from torchvision import transforms

to_pil=transforms.ToPILImage()
to_pil(t.randn(3,64,64))

output_44_0.png

torchvision还提供了两个常用的函数。一个是make_grid,它能将多张图片拼接在一个网格中;另一个是save_image,它能将Tensor保存成图片

len(dataset)
10000
dataloader=DataLoader(dataset,shuffle=True,batch_size=16)

from torchvision.utils import make_grid,save_image

dataiter=iter(dataloader)
img=make_grid(next(dataiter)[0],4)

to_img(img)
save_image(img,'a.png')

Image.open('a.png')

三.可视化工具

介绍两个深度学习中常用的可视化工具:Tensorboard和visdom

1.Tensorboard

最初,Tensorboard是作为Tensorflow的可视化工具迅速流行起来的。作为和TensorFlow深度集成的工具,Tensorboard能够展现Tensorflow网络计算图,绘制图像生成的定量指标图及附加数据。在PyTorch中使用Tensorboard_logger进行训练损失的可视化。Tensorboard_logger是TeamHG-Memex开发的一款轻量级工具,它将Tensorboard的功能抽取出来,使非Tensorflow用户也能使用它进行可视化,但其支持的功能有限

tensorboard_logger的安装主要分为以下两步:

  • 安装tensorflow:建议安装CPU-Only的版本
  • 安装tensorboard_logger:使用pip install tensorboard_logger

tensorboard_logger的使用非常简单。首先用如下命令启动Tensorboard:

tensorboard --logdir <your/running/dir> --port <your_bind_port>
from tensorboard_logger import Logger
# 构建logger对象,logdir用来指定log文件的保存路径
# flush_secs用来指定刷新同步间隔
logger=Logger(logdir='experimient_cnn',flush_secs=2)
for ii in range(100):
    logger.log_value('loss',10-ii**0.5,step=ii)
    logger.log_value('accuracy',ii**0.5/10)

输入:tensorboard --logdir F:\Python\Pytest\DL\pytorch\experimient_cnn --port 6006,打开浏览器http://localhost:6006

image.png

左侧的Horizontal Axis下有如下三个选项:

  • step:根据步长来记录,log_value是指如果有步长,则将其作为x轴坐标描点画线
  • Relative:用前后相对顺序描点画线,可认为logger自己维护了一个step属性,每调用一次log_value就自动加1
  • Wall:按时间排序描点画线

左侧的Smoothing条可以左右拖动,用来调节平滑的幅度。单击页面右上角的刷新按钮可立即刷新结果,默认是每30秒自动刷新新数据。Tensorboard_logger的使用十分简单,但它只能统计简单的数值信息,不支持其他功能

除了tensorboard_logger,还有专门针对PyTorch开发的TensorboardX,它封装更多的Tensorboard接口,支持记录标量,图片,直方图,声音,文本,计算图和embedding等信息,几乎包括和Tensorflow的Tensorboard完全一样的功能

2.visdom

visdom是Facebook专门为PyTorch开发的一款可视化工具,visdom十分轻量级,却支持非常丰富的功能,能胜任大多数的科学运算可视化任务

visdom中有以下两个重要概念:

  • env:环境。不同环境的可视化结果相互隔离,互不影响,在使用时如果不指定env,默认使用main
  • pane:窗格。窗格可用于可视化图像,数值或打印文本等,其可以拖动,缩放,保存和关闭

通过命令pip install visdom即可完成visdom的安装。安装完成后,需通过python -m visdom.server命令启动visdom服务

import visdom

# 新建一个连接客户端
# 指定env=u'test1',默认端口为8097,host为'localhost'
vis=visdom.Visdom(env=u'test1')

x=t.arange(1,30,0.01)
y=t.sin(x)
vis.line(X=x,Y=y,win='sinx',opts={'title':'y=sin(x)'})
WARNING:root:Setting up a new session...

'sinx'

37244307c7fa7a.svg

下面我们逐一分析这几行代码:

  • vis=visdom.Visdom(env=u’test1’):用于构建一个客户端,客户端除指定env外,还可以指定host,port等参数
  • vis作为一个客户端对象,可以使用如下常见的画图函数
    • line:类似MATLAB中的plot操作,用于记录某些标量的变化,例如损失,准确率等
    • image:可视化图片,可以是输入的图片,也可以是GAN生成的图片,还可以是卷积核的信息
    • text:用于记录日志等文字信息,支持HTML格式
    • histgram:可视化分布,主要是查看数据,参数的分布
    • scatter:绘制散点图
    • bar:绘制柱状图
    • pie:绘制饼状图

3724469a9a9450.svg

image的画图功能可分为如下两类:

  • iamge接收一个二维或三维向量,H_W或3_H*W,前者是黑白图像,后者是彩色图像
  • images接收一个四维向量N_C_H*W,C可以是1或3,分别代表黑白和彩色图像
# 可视化一张随机的黑白图片
vis.image(t.randn(64,64).numpy())

# 可视化一张随机的彩色图片
vis.image(t.randn(3,64,64).numpy(),win='random2')

# 可视化36张随机的彩色图片,每一张6张
vis.images(t.randn(36,3,64,64).numpy(),nrow=6,win='random3',opts={'title':'random_imgs'})
'random3'

random_imgs.jpg

vis.text用于可视化文本,它支持所有的html标签

vis.text(u'''<h1>Hello visdom</h1><br>visdom是Facebook专门
为<b>PyTorch</b>开发的一款可视化工具,
visdom十分轻量级,却支持非常丰富的功能,
能胜任大多数的科学运算可视化任务''',win='visdom',opts={'title':u'vidsom简介'})
'visdom'

image.png

posted @ 2019-03-09 11:47  LQ6H  阅读(1158)  评论(0编辑  收藏  举报