pytorch深入探索续篇

前言

  由于前一段时间在搞定位算法,暂时没有时间来学习、记录pytorch的学习过程。在这里就先提及需要详细解释的一些部分

  • 优化器optimizer的种类、各自的优势,以及我应该如何选取
  • 正则化、过拟合、batch normalization的解释和应用
  • 学习更多的深度学习算法来更好的应用在实际例子中
  • 更多pytorch例程的研究记录

pytorch深入了解

前言

  经过这几天的零碎学习,我真是觉得神经网络的搭建真的太复杂了,复杂到什么程度呢?就是一个概念不懂,找到解释后,发现解释里又包含着好几个你根本不知道的概念,不过还好我做了一些学习准备,也不算是完全没有头绪。但是目前暴露的缺陷有:

  • python高级语法知识的欠缺
  • pytorch搭建神经网络的细节问题不懂
  • 对于神经网络的每一个相关细节概念的理解和了解不足

  那么针对这些,我认为可以在学习一些实例、教程时碎片化的学习来完善自己的知识框架,所以有了以下的“深入了解”学习阶段

学习阶段————深入了解

思考:

  • 神经网络的框架如何根据自己的要求、向适合解决问题的网络结构改进?
  • 学习如何使用GPU来训练模型?
  • jupyter notebook 中如何正常运行pytorch网络?

二、结合实例理解,弥补缺失的框架体系知识

1. 读取文件必要的DatasetDataLoader

  •   获取数据一直应该都是神经网络的一个要点。有些时候可以通过某些官方渠道的统一数据集来进行训练。但对于实际的项目,往往无法这样做,那么对于这一次我的大作业也就是获取图像信息来说,每一张图像的数据不一样,尺寸也都不一样,如何把这些杂乱的数据统一的放入神经网络中学习呢?在这里DatasetDataLoader就起到了必要的作用
  • DatasetDataLoader分别对应数据的读取和操作,他们是官网提供给我们处理数据的一个范例。

Dataset

  • Dataset位于torch.utils.data.Dataset中,往往我们需要创建自己的获取数据集的Mydataset类,他必须继承官方的Dataset类,并且必须要实现其两个成员函数:__len__()__getitem__()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import torch
import cv2
from torch.utils.data import Dataset

class datasets(Dataset):
def __init__(self,data,transform = None,test = False):
imgs = []
labels = []
self.test = test
self.len = len(data)
self.data = data
self.transform = transform
for i in self.data:
imgs.append(i[0])
self.imgs = imgs
labels.append(int(i[1]) ) #pytorch中交叉熵需要从0开始
self.labels = labels
def __getitem__(self,index):
if self.test:
filename = self.imgs[index]
filename = filename
img_path = self.imgs[index]
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (config.img_width, config.img_height))
img = transforms.ToTensor()(img)
return img,filename
else:
img_path = self.imgs[index]
label = self.labels[index]
#label = int(label)
img = cv2.imread(img_path)
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img = cv2.resize(img,(config.img_width,config.img_height))
# img = transforms.ToTensor()(img)

if self.transform is not None:
img = Image.fromarray(img)
img = self.transform(img)

else:
img = transforms.ToTensor()(img)
return img,label

def __len__(self):
return len(self.data)#self.len
  • 在这个实例中,在初始化时需要的参数是,需要训练或者测试的数据,transform操作(这个具体在后面说明),至于后面的test参数也和transform有关
  • 重点看类里内置的方法

    • __getitem__(self,index):该方法支持从 0 到len(self)的索引
        在我们自己的Dataset中必须需要一个这个方法获取数据集中的每一组数据。比如在此例中,由于在建立dataset实例时出入的data数据是所有的.jpg文件,那么当我们需要获取图片进行训练或测试时,需要利用opencv的方法读取图片数据、将图片转换成RGB格式,把每个图片的尺寸统一为设定的模板尺寸,最后将图片信息数据转换为tensor张量形式

    • __len__(self)
        这个方法比较简单,用起来也很方便。目的就是,返回数据集的长度。但这个方法必须有。

  • 现存问题:在初始化时,类里面的一些赋值操作,是否存在必要性。当然,数据必须保存在数据集中,所以一个循环读取全部数据内容的操作应该要有。所以我猜测是自己如果需要用到就加,如果不会用到就不需要写在初始化方法中。

DataLoader

  • torch.utils.data已经提供的类:Dataset,但是通过这种方式只能一个个的数据的把数据全部读出来,定义了数据读取的方式,不能实现批量的把数据读取出来,为此pytorch有提供了一个方法:DataLoader()
      简单点说,就是通过DataLoaderDataset中的数据集以每次Batch_size个大小的组,读取出来,以供训练
  • Dataloader本质是一个可迭代对象,使用iter()访问,不能使用next()访问,由于它本身就是一个可迭代对象,可以使用for inputs, labels in dataloaders进行可迭代对象的访问
  • 我们一般不需要再自己去实现DataLoader的方法了,只需要在构造函数中指定相应的参数即可,比如常见的batch_sizeshuffle等等参数。所以使用DataLoader十分简洁方便。
1
2
3
4
5
6
7
8
9
10
 #自己定义的函数:获取数据并将数据分为训练集和测试集两类
test_list , train_list = get_files(config.data_folder,config.ratio)

#训练集 需要数据增强 transform 使用之前定义好的
input_traindata = datasets(train_list,transform = transform)
train_loader = DataLoader(input_traindata,batch_size = config.batch_size,shuffle = True,collate_fn = collate_fn ,pin_memory = False,num_workers = 4)

#测试集 不要数据增强 transform = None
input_testdata = datasets(test_list,transform = None)
test_loader = DataLoader(input_testdata,batch_size = config.batch_size,shuffle = False,collate_fn = collate_fn,num_workers = 4)
  • 上述例子可以看到,根据自己定义的dataset创建了训练和测试两个实例,训练需要数据增强而测试不需要,所以两个实例除数据不同,tranform也不同
  • DataLoader的参数
    • X_dataset:数据集的实例
    • batch_size:训练的提取数据的单位内所含个数
    • shuffle :布尔值True或者是False ,表示每一个epoch之后是否对样本进行随机打乱,默认是False
    • collate_fn:这个是一个问题,因为通常由自己确定。是一个自己对于数据选取的方法,但是我不知道他有什么意义,或者优势
    • pin_memory:如果设置为True,那么data loader将会在返回它们之前,将tensors拷贝到CUDA中的固定内存(CUDA pinned memory)中
    • num_workers:这个参数决定了有几个进程来处理data loading。0意味着所有的数据都会被load进主进程。(默认为0)

1.2 典型的批训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import torch
import torch.utils.data as Data

torch.manual_seed(1) # reproducible

BATCH_SIZE = 5

x = torch.linspace(1, 10, 10) # this is x data (torch tensor)
y = torch.linspace(10, 1, 10) # this is y data (torch tensor)

torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(
dataset=torch_dataset, # torch TensorDataset format
batch_size=BATCH_SIZE, # mini batch size
shuffle=True, # random shuffle for training
num_workers=2, # subprocesses for loading data
)

def show_batch():
for epoch in range(3): # train entire dataset 3 times
for step, (batch_x, batch_y) in enumerate(loader): # for each training step
# train your data...

print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())

if __name__ == '__main__':
show_batch()
  • 不难发现,其实批训练的实现就是对DatasetDataLoader的使用,这里很简单,只是看一个思路。
  • 那么其实每一次训练的时候,对于loader实例的索引,他将带出一组Batch_size个数的数据,但是你的神经网络的输入层的结构不会因为batch_size的变化受到影响
  • 批训练中batch_size对CNN结果的影响,点击这里了解

1.3 collate_fn如何在加载数据时起作用?

  • DataLoader能够为我们自动生成一个多线程的迭代器,只要传入几个参数进行就可以了,但是你真的懂得它是如何工作的么?

问题就在这个collate_fn

  • collate_fn默认是等于default_collate
1
2
3
4
5
6
7
def __next__(self):    #这是DataLoader的关于迭代的方法的源码
if self.num_workers == 0: # same-process loading
indices = next(self.sample_iter) # may raise StopIteration
batch = self.collate_fn([self.dataset[i] for i in indices]) # 在这里调用了collate_fn函数,传递的参数是一个列表
if self.pin_memory:
batch = pin_memory_batch(batch)
return batch
  • 那么其实collate_fn这个函数的输入就是一个list,list的长度是一个batch size,list中的每个元素都是__getitem__得到的结果,想要知道详解点击这里,那么batch中的每一个元素其实就是Dataset__getitem__方法得到的比如(图片信息img、标签label)
  • 那么如果理解了以上的意思,再看collate_fn就能较快地上手了

  • 那什么时候该使用DataLoader的collate_fn这个参数?
      当定义DataSet类中的__getitem__函数的时候,由于每次返回的是一组类似于(x,y)的样本,但是如果在返回的每一组样本x,y中出现什么错误,或者是还需要进一步对x,y进行一些处理的时候,我们就需要再定义一个collate_fn函数来实现这些功能。当然我也可以自己在实现__getitem__的时候就实现这些后处理也是可以的。

  • collate_fn,中单词collate的含义是:核对,校勘,对照,整理。顾名思义,这就是一个对每一组样本数据进行一遍“核对和重新整理”,现在可能更好理解一些


2.1 torchvision是什么?

  • torchvision是PyTorch中专门用来处理图像的库,PyTorch官网的安装教程,也会让你安装上这个包。
  • 这个包中有四个大类
    • torchvision.datasets
    • torchvision.models
    • torchvision.transforms
    • torchvision.utils

torchvision.datasets

  • torchvision.datasets 是用来进行数据加载的,PyTorch团队在这个包中帮我们提前处理好了很多很多图片数据集。

    • MNISTCOCO
    • Captions
    • Detection
    • LSUN
    • ImageFolder
    • Imagenet-12
    • CIFAR
    • STL10
    • SVHN
    • PhotoTour
  • 我们可以直接使用,示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import torchvision

    trainset = torchvison.datasets.MNIST(
    root = './data', #表示MNIST数据的加载的相对目录
    train = True, #表示是否加载数据库的训练集,false的时候加载测试集
    download = True#表示是否自动下载MNIST数据集
    transform = None #表示是否需要对数据进行预处理,None为不进行预处理
    )
    # 上面代码完成了MNIST数据 训练集的加载环节
    train_loader2 = Dateloader(
    dataset = trainset,
    batch_size = 32,
    shuffle = True
    )
    print("训练集总长度为:",len(trainset))
    print("每个mini-batch的size为32,一共有:",len(train_loader2),"个")

torchvision.models

  • torchvision.models中为我们提供了已经训练好的模型,让我们可以加载之后,直接使用

  • torchvision.models模块的子模块中包含以下模型结构。

    • AlexNet
    • VGG
    • ResNet
    • SqueezeNet
    • DenseNet
  • 我们可以直接使用如下代码来快速创建一个权重随机初始化的模型

    1
    2
    3
    4
    5
    6
    import torchvision.models as models

    resnet18 = models.resnet18()
    alexnet = models.alexnet()
    squeezenet = models.squeezenet1_0()
    densenet = models.densenet_161()
  • 也可以通过使用pretrained=True来加载一个别人预训练好的模型

    1
    2
    3
    4
    import torchvision.models as models

    resnet18 = models.resnet18(pretrained=True)
    alexnet = models.alexnet(pretrained=True)

torchvision.transforms

  • transforms提供了一般的图像转换操作类,这就是我在一开始Dataset中提到了transform,还记得么?哈哈,可以翻一翻上面的例程中在创建Mydataset类的getitem方法中是如何使用transforms的

  • 其实我猜测例程中,作者想在获取每一个图像信息时,除了进行opencv的操作外,训练时还要对图像数据信息做更多处理,而测试时当然就不需要这种处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 我们这里还是对MNIST进行处理,初始的MNIST是 28 * 28,我们把它处理成 96 * 96 的torch.Tensor的格式
from torchvision import transforms as transforms
import torchvision
from torch.utils.data import DataLoader

# 图像预处理步骤
transform = transforms.Compose([
transforms.Resize(96), # 缩放到 96 * 96 大小
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 归一化
])

DOWNLOAD = True
BATCH_SIZE = 32

train_dataset = torchvision.datasets.MNIST(root='./data/', train=True, transform=transform, download=DOWNLOAD)
train_loader = DataLoader(dataset=train_dataset,
batch_size=BATCH_SIZE,
shuffle=True)

print(len(train_dataset))
print(len(train_loader))
  • 例程中是将从torchvision.datasets中获取的图像数据做transform,这样更简单,那如何在自己定义的Dataset类中加入Transform呢?
    • 第一步:定义自己的DataSet,并重写__getitem__,在里面实现关键的transform操作
    • 第二步:将多个数据增强方式组合起来合成一个transforms,通过Compose类来实现,注意这个类的返回值哦!
    • 第三步:构造DataSet的对象,将组合起来的transform传递进去,这样就会对每一个batch_size的图像都进行相关的数据增强操作了
  • 细节问题在于,我没太搞懂这种的数据类型的转换,一会是图片类型,一会是numpy矩阵类型,最后又要转换成tensor张量类型,这个要之后细究

3.1 什么是Resnet

👴也不知道,👴正在学

pytorch使用说明

torch.normal(means, std, out=None)

  • 返回满足正态分布的张量
  • meansstd分别给出均值和标准差

torch.rand(*sizes, out=None) → Tensor

  • 均匀分布:返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。张量的形状由参数sizes定义

torch.randn(*sizes, out=None) → Tensor

  • 标准正态分布:返回一个张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。张量的形状由参数sizes定义

torch.squeeze(a,N)

  • 对数据的维度进行压缩,去掉指定个维数为1的维度,注意:如果不加第二个参数’N’,就是去掉所有维数为1的维度
  • 另一种形式,a.squeeze(N) 也是去掉a中指定的维数为一的维度,去掉的维度数为N

torch.unsqueeze()

  • 对数据维度进行扩充。给指定位置加上维数为1的维度,同样有另外一种形式a.squeeze(N)
  • 注意!当对神经网络的输入层的特征数据提前处理时,可能需要使用该函数,因为输入数据必须是一个至少二维的张量

可以关注一下python自带的numpy数字计算库和torch的数据处理,其实相似度很高


torch.cat((A,B),0)

  • 将两个张量(tensor)拼接在一起,第一个参数是两个张量组成的元组,第二个参数是行拼接(1)或者列拼接(0)
  • 实例

np.hstack((A,B,C..)) & np.vstack((A,B,C..))

  • 将参数元组的元素数组按水平/垂直方向进行叠加,可以和上面的torch.cat比较一下

torch.stack([tensor1, tensor2, tensor3…], dim=0)

  • 将几个矩阵按照dim指定维度堆叠在一起
  • 实例

np.random.shuffle(x)

  • 现场修改序列,改变自身内容。(类似洗牌,打乱顺序)
  • 对多维数组进行打乱排列时,默认是对第一个维度也就是列维度进行随机打乱

np.transpose(x)

  • 常用于二维数组的转置

torch.div(input, value, out=None)

  • 将input逐元素除以标量值value,并返回结果到输出张量out。

plt.scatter(x,y,c='r',s=20,marker=".",lw=2)

  • matplotlib库中画散点图的函数,注意c这个参数的特别用法
  • 详解
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×