读取数据 DataLoader

pytorch读数据,目前来看,DataLoader是比较常用的一种方式,采用这种方法有3步:

  1. 创建一个DataSet对象,继承自dataset类
  2. 创建一个DataLoader对象
  3. 从这个DataLoader对象中,循环读取数据

按我目前的理解,首先我们需要把全量数据在自己定义的DataSet类中,定义好每次迭代的方法,然后就用DataLoader进行加载即可。可以把所有的数据都放到DataSet类里进行处理,保证DataLoader返回的就是直接可以输入模型训练的样本。
示例:

  1. # coding: utf-8
  2. from PIL import Image
  3. from torch.utils.data import Dataset
  4. class MyDataset(Dataset):
  5. def __init__(self, txt_path, transform = None, target_transform = None):
  6. fh = open(txt_path, 'r')
  7. imgs = []
  8. for line in fh:
  9. line = line.rstrip()
  10. words = line.split()
  11. imgs.append((words[0], int(words[1])))
  12. self.imgs = imgs
  13. self.transform = transform
  14. self.target_transform = target_transform
  15. def __getitem__(self, index):
  16. fn, label = self.imgs[index]
  17. img = Image.open(fn).convert('RGB')
  18. if self.transform is not None:
  19. img = self.transform(img)
  20. return img, label
  21. def __len__(self):
  22. return len(self.imgs)

代码是从一个文本中,读取图片的地址和对应的label,将其存入到self.imgs里,然后在迭代的时候,调用getitem(),根据index从self.imgs里取出图片和对应的label,并用PIL.Image.open打开图片,转换成RGB形式。
在调用的时候只需要:

  1. train_loader = DataLoader(MyDataset(txt_path),
  2. batch_size=batch_size,
  3. shuffle=False,
  4. drop_last=True)
  5. for data in train_loader:
  6. img, label = data

示例:
https://zhuanlan.zhihu.com/p/105507334
这里有官方MNIST的例子,从这里里面,我们可以学到的是如何从拿到图片地址->下载图片->读取图片并加载到模型进行训练的一个完整过程。如果你有类似的任务,或者说像我当前这个项目一样,需要用到图片和文本的联合训练,那么也许可以把他们组织成一个样本,然后通过DataLoder每次返回,不仅返回图片,还有文本信息和标签信息,这样就可以直接进行下一步训练,并且不用关心图片和文本是否对应的上这样的问题。

参考:

tensor

tensor的一些操作

https://www.jianshu.com/p/314b6cfce1c3

tensor维度转换(拼接,新增,拆分)

  • 维度拼接 torch.cat

    1. a = torch.rand(4, 32, 8)
    2. b = torch.rand(5, 32, 8)
    3. c = torch.cat([a, b], dim=0) # 按第0维度进行拼接,除拼接之外的维度必须相同
    4. print(c.shape)
    5. # 结果:torch.Size([9, 32, 8])
  • 产生一个新维度 torch.stack

    1. a = torch.rand(5, 32, 8)
    2. b = torch.rand(5, 32, 8)
    3. c = torch.rand(5, 32, 8)
    4. d = torch.stack([a, b, c], dim=0) # 产生一个新的维度,待拼接的向量维度相同
    5. print(d.shape)
    6. # 结果:torch.Size([3, 5, 32, 8])
  • 按指定长度拆分 split

    1. a = torch.rand(6, 32, 8)
    2. b, c = a.split(3, dim=0) # 所给的是拆分后,每个向量的大小,指定拆分维度
    3. print(b.shape)
    4. print(c.shape)
    5. # 结果:
    6. # torch.Size([3, 32, 8])
    7. # torch.Size([3, 32, 8])
  • 按给定数量拆分 chunk

    1. a = torch.rand(6, 32, 8)
    2. b, c, d = a.chunk(3, dim=0) # 所给的是拆分的个数,即拆分成多少个
    3. print(b.shape)
    4. print(c.shape)
    5. # 结果:
    6. # torch.Size([2, 32, 8])
    7. # torch.Size([2, 32, 8])

    替换tensor中大于某个值的所有元素

    ```python

    import torch a = torch.rand(2,3) a tensor([[0.6976, 0.9647, 0.2891],

    1. [0.4800, 0.5177, 0.6486]])

    b = torch.zeros_like(a) c = torch.ones_like(a)

torch.where(a > 0.5, b, c) tensor([[0., 0., 1.], [1., 0., 0.]])

torch.where(a > 0.5, b, a) tensor([[0.0000, 0.0000, 0.2891], [0.4800, 0.0000, 0.0000]]) ```

提取中间层特征

参考:

保存和加载模型

保存的文件是否一定要有扩展名?
我自己用的时候,没有扩展名,也是可以载入的,看起来没啥影响
保存分为两种:

  1. 只保存参数
  2. 保存整个模型(结构+参数)

这里介绍的是第一种,也是官方推荐,所以在这种模式下,保存的只是模型参数,所以在使用的时候,需要先定义一个跟保存参数时的模型一样的模型,然后生成实例,用实例去恢复参数。

快速使用

  1. # 1. 只保存参数
  2. # save
  3. torch.save(model.state_dict(), path)
  4. # load
  5. model.load_state_dict(torch.load(path))
  6. # 也可以同时保存其他信息
  7. state = {'model': model.state_dict(), 'optimizer': optimizer.state_dict(), 'epoch': epoch}
  8. torch.save(state, path)
  9. # load
  10. checkpoint = torch.load(path)
  11. model.load_state_dict(checkpoint['model'])
  12. optimizer.load_state_dict(checkpoint['optimizer'])
  13. epoch = checkpoint(['epoch'])
  14. # 2. 保存模型
  15. torch.save(model, './model.pkl')
  16. model = torch.load('./model.pkl')

深入理解

首先,torch.save是可以保存各种对象的,tensor,list,map,object等都可以

  1. import torch.nn as nn
  2. class MLP(nn.Module):
  3. def __init__(self):
  4. super(MLP, self).__init__()
  5. self.hidden = nn.Linear(3, 2)
  6. self.relu = nn.ReLU()
  7. self.output = nn.Linear(2, 1)
  8. def forward(self, x):
  9. x = self.hidden(x)
  10. x = self.relu(x)
  11. x = self.output(x)
  12. return x
  13. net = MLP()
  14. print(net.state_dict())
  15. -- output
  16. OrderedDict([('hidden.weight', tensor([[-0.3975, 0.2310, -0.1084], [ 0.0693, -0.3207, -0.3304]])),
  17. ('hidden.bias', tensor([ 0.1656, -0.0796])),
  18. ('output.weight', tensor([[-0.3187, 0.3556]])),
  19. ('output.bias', tensor([0.4653]))])
  20. print(list(net.parameters()))
  21. -- output
  22. [Parameter containing: tensor([[-0.3975, 0.2310, -0.1084], [ 0.0693, -0.3207, -0.3304]], requires_grad=True),
  23. Parameter containing: tensor([ 0.1656, -0.0796], requires_grad=True),
  24. Parameter containing: tensor([[-0.3187, 0.3556]], requires_grad=True),
  25. Parameter containing: tensor([0.4653], requires_grad=True)]
  • 对net.state_dict() 这是net.state_dict()是一个字典,且是一个有序字典,存储的是有参数的层。
  • 对net.parameters()返回一个迭代器,存的也是可训练的参数,所以看起来,这俩没啥区别
  1. # load
  2. pretrained_dict = torch.load(log_dir) # 加载参数字典
  3. model_state_dict = model.state_dict() # 加载模型当前状态字典
  4. pretrained_dict_1 = {k:v for k,v in pretrained_dict.items() if k in model_state_dict} # 过滤出模型当前状态字典中没有的键值对
  5. model_state_dict.update(pretrained_dict_1) # 用筛选出的参数键值对更新model_state_dict变量
  6. model.load_state_dict(model_state_dict) # 将筛选出的参数键值对加载到模型当前状态字典中

从这里可以看出,理解了模型的参数保存,只是保存了一个字典以后,在加载的时候,就可以先加载参数字典,然后将参数字典更新给现在的模型,或者需要对原来保存的参数进行部分修改,也可以直接修改这里的pretrained_dict,然后加载给模型即可。

参考:

构建模型的方法

构建模型有两种方法:

  1. 采用class 方式定义Model,通过foward调用
  2. 采用torch.nn.Sequential快速搭建

示例:

  1. # 普通方法
  2. class Net(torch.nn.Module):
  3. def __init__(self, n_feature, n_hidden, n_output):
  4. super(Net, self).__init__()
  5. self.hidden = torch.nn.Linear(n_feature, n_hidden)
  6. self.predict = torch.nn.Linear(n_hidden, n_output)
  7. def forward(self, x):
  8. x = F.relu(self.hidden(x))
  9. x = self.predict(x)
  10. return x
  11. net1 = Net(1, 10, 1)
  12. # 快速方法
  13. net2 = torch.nn.Sequential(
  14. torch.nn.Linear(1, 10),
  15. torch.nn.ReLU(),
  16. torch.nn.Linear(10, 1)
  17. )
  18. print(net1)
  19. """
  20. Net (
  21. (hidden): Linear (1 -> 10)
  22. (predict): Linear (10 -> 1)
  23. )
  24. """
  25. print(net2)
  26. """
  27. Sequential (
  28. (0): Linear (1 -> 10)
  29. (1): ReLU ()
  30. (2): Linear (10 -> 1)
  31. )
  32. """

为什么tensor要区分cpu和gpu?

原因主要是cpu张量的参数存在内存中,gpu张量存在显存中,当你在gpu中运行一个运算时,因为gpu只能计算存放在显存中的数据,因此你就需要将cpu张量转换为gpu张量,不然该张量就会找不到,所以cpu张量和gpu张量之间的转换,需要进行内存/显存交换。

另外一个场景就是,当你在用GPU进行训练,而运算结果需要存在列表中时,默认是会将结果保存在显存中,这有可能导致GPU训练超出内存,所以建议将运算结果得到的GPU张量转换为CPU张量,再存放在列表中。

下面通过一个小实验来看一下:

  1. import torch
  2. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  3. print("device:", device)
  4. a = torch.randn((3,1))
  5. b = torch.randn((3,1))
  6. c = a + b
  7. print("a:", a.device)
  8. print("b:", b.device)
  9. print("c:", c.device)
  10. b = b.to(device)
  11. d = a + b
  12. print("a:", a.device)
  13. print("b:", b.device)
  14. print("d:", d.device)
  15. -- output
  16. device: cuda
  17. a: cpu
  18. b: cpu
  19. c: cpu
  20. Traceback (most recent call last):
  21. File "ca87f0b1-1171-4453-9e39-ad981e327e12_test.py", line 17, in <module>
  22. d = a + b
  23. RuntimeError: expected type torch.FloatTensor but got torch.cuda.FloatTensor

这里就看的很明确了,首先,默认的tensor是建立在cpu上的,当明确指定转换为gpu上的时(13行),此时再进行cpu和gpu上tensor的混合运算就会报错,此时就必须统一转换为cpu或gpu上的tensor。

分布式训练

https://zhuanlan.zhihu.com/p/113694038