1. 数据处理模块
- 自定义数据集需要继承Dataset类,并实现两个魔法方法
__getitem__:返回一条数据,或一个样本。obj[index]等价于obj.__getitem__(index)__len__:返回样本的数量。len(obj)等价于obj.__len__()- 进一步:torchvision的transform模块提供对pil image和tensor对象的操作:
对PIL Image的操作包括:
Scale:调整图片尺寸,长宽比保持不变CenterCrop、RandomCrop、RandomResizedCrop: 裁剪图片Pad:填充ToTensor:将PIL Image对象转成Tensor,会自动将[0, 255]归一化至[0, 1]!!!
对Tensor的操作包括:
- Normalize:标准化,即减均值,除以标准差
- ToPILImage:将Tensor转为PIL Image对象
如果要对图片进行多个操作,可通过Compose函数将这些操作拼接起来,类似于nn.Sequential。
5.1 torch.utils.data.Dataset
from torchvision import transforms as T
from torch.utils import data
transform = T.Compose([
T.Resize(224),
T.CenterCrop(224),
T.ToTensor(),
T.Normalize(mean = [.5, .5, .5], std = [.5, .5, .5])#对tensor的操作
])
class DogCat(data.Dataset):
def __init__(self, root, transform):
imgs = os.listdir(root)
self.imgs = [os.path.join(root, img) for img in imgs]#每张图片的路径组成的列表
self.transforms - transform
def __getitem__(self, index):
img_path = self.imgs[index]
label = 1 if 'dog' in img_path.split('/')[-1] else 0;
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/dogcat')
img,label = dataset[0]
for img,label in dataset:#自动迭代!!!
print(img.size(),label)
1.1 from torchvision.datasets.ImageFolder
#猫狗二分类2:不同类别在不同文件夹
from torchvision.datasets import ImageFolder
dataset = ImageFolder('data_dogcat/dogcat_2')
dataset.class_to_idx# cat文件夹的图片对应label 0,dog对应1
dataset.imgs# 所有图片的路径和对应的label
# 没有任何的transform,所以返回的还是PIL Image对象
print(dataset[0][1])
dataset[1][0]# 第一维是第几张图,第二维为1返回label
#加上transform
dataset = ImageFolder('data_dogcat/dogcat_2/', transform=transform)
dataset[0][0].size()#tensor,可以看size()3维
to_img = T.ToPILImage()
to_img(dataset[0][0]*0.5+0.5)
1.2 从__getitem__一个样本到batch: torch.utils.data.Dataloader
DataLoader的函数定义如下:
DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False)
- dataset:加载的数据集(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)
#调用dataloader
dataiter = iter(dataloader)
imgs,labels = next(dataiter)
#或者for batch_datas,batch_labels in dataloader:
imgs.size()
1.2.1 sampler
RandomSampler,当dataloader的shuffle参数为True时,系统会自动调用这个采样器,实现打乱数据。默认的是采用SequentialSampler,它会按顺序一个一个进行采样。这里介绍另外一个很有用的采样方法:
WeightedRandomSampler,它会根据每个样本的权重选取数据,在样本比例不均衡的问题中,可用它来进行重采样。
构建WeightedRandomSampler时需提供两个参数:每个样本的权重weights、共选取的样本总数num_samples,以及一个可选参数replacement。权重越大的样本被选中的概率越大,待选取的样本数目一般小于全部的样本数目。replacement用于指定是否可以重复选取某一个样本,默认为True,即允许在一个epoch中重复采样某一个数据。如果设为False,则当某一类的样本被全部选取完,但其样本数目仍未达到num_samples时,sampler将不会再从该类中选择数据,此时可能导致weights参数失效。
dataset = DogCat('data_dogcat/dogcat/', transform=transform)
# 狗的图片被取出的概率是猫的概率的两倍
# 两类图片被取出的概率与weights的绝对大小无关,只和比值有关
weights = [2 if label == 1 else 1 for data, label in dataset]
from torch.utils.data.sampler import WeightedRandomSampler
sampler = WeightedRandomSampler(weights,\
num_samples=9,\
replacement=True)
#8个样本,返回了9个,说明有重复返回的,可以设置replacement=False
dataloader = DataLoader(dataset,
batch_size=3,
sampler=sampler)
for datas, labels in dataloader:
print(labels.tolist())
1.3 torchvision
torchvision主要包含三部分:
- models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括
AlexNet、VGG系列、ResNet系列、Inception系列等。 - datasets: 提供常用的数据集加载,设计上都是继承
torhc.utils.data.Dataset,主要包括MNIST、CIFAR10/100、ImageNet、COCO等。dataset = datasets.MNIST('data/', download=True, train=False, transform=transform) - transforms:提供常用的数据预处理操作,主要包括对Tensor以及PIL Image对象的操作。
1.4 tensorboard
1.5 gpu
- 数据转移至gpu调用.cuda,因为gpu的编程接口采用cuda,但不是所有gpu都支持cuda,只有部分nvidia的才支持,如果是amd的gpu,预留了一个.cl
- **1.tensor.cuda()**是返回的新对象,存在于gpu,原对象存在于cpu
- **2.nn.module.cuda()**把自己迁移到gpu,并返回自己
- 避免使用cuda(1)这样调用,可设置环境变量:设置
CUDA_VISIBLE_DEVICES有两种方法,一种是在命令行中CUDA_VISIBLE_DEVICES=0,1 python main.py,一种是在程序中import os;os.environ["CUDA_VISIBLE_DEVICES"] = "2"。如果使用IPython或者Jupyter notebook,还可以使用%env CUDA_VISIBLE_DEVICES=1,2来设置环境变量。
1.6 保存模型
- t.save(a,'a.pth')
- t.load('a.pth')
from torchvision.models import SqueezeNet
model = SqueezeNet()
# module的state_dict是一个字典
model.state_dict().keys()
# Module对象的保存与加载
t.save(model.state_dict(), 'squeezenet.pth')
model.load_state_dict(t.load('squeezenet.pth'))#先load进所有的键-值对,再load出state_dict
optimizer = t.optim.Adam(model.parameters(), lr=0.1)
t.save(optimizer.state_dict(), 'optimizer.pth')
optimizer.load_state_dict(t.load('optimizer.pth'))
all_data = dict(
optimizer = optimizer.state_dict(),
model = model.state_dict(),
info = u'模型和优化器的所有参数'
)
t.save(all_data, 'all.pth')
all_data = t.load('all.pth')
all_data.keys()#dict_keys(['optimizer', 'model', 'info'])
2. 程序结构
大多数深度学习研究时,程序都需要实现以下几个功能:
- 模型定义
- 数据处理和加载
- 训练模型(Train&Validate)
- 训练过程的可视化
- 测试(Test/Inference)
另外程序还应该满足以下几个要求:
- 模型需具有高度可配置性,便于修改参数、修改模型,反复实验
- 代码应具有良好的组织结构,使人一目了然
- 代码应具有良好的说明,使其他人能够理解
首先来看程序文件的组织结构:
├── 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:提供程序的必要说明