读取数据 DataLoader
pytorch读数据,目前来看,DataLoader是比较常用的一种方式,采用这种方法有3步:
- 创建一个DataSet对象,继承自dataset类
- 创建一个DataLoader对象
- 从这个DataLoader对象中,循环读取数据
按我目前的理解,首先我们需要把全量数据在自己定义的DataSet类中,定义好每次迭代的方法,然后就用DataLoader进行加载即可。可以把所有的数据都放到DataSet类里进行处理,保证DataLoader返回的就是直接可以输入模型训练的样本。
示例:
# coding: utf-8from PIL import Imagefrom torch.utils.data import Datasetclass MyDataset(Dataset):def __init__(self, txt_path, transform = None, target_transform = None):fh = open(txt_path, 'r')imgs = []for line in fh:line = line.rstrip()words = line.split()imgs.append((words[0], int(words[1])))self.imgs = imgsself.transform = transformself.target_transform = target_transformdef __getitem__(self, index):fn, label = self.imgs[index]img = Image.open(fn).convert('RGB')if self.transform is not None:img = self.transform(img)return img, labeldef __len__(self):return len(self.imgs)
代码是从一个文本中,读取图片的地址和对应的label,将其存入到self.imgs里,然后在迭代的时候,调用getitem(),根据index从self.imgs里取出图片和对应的label,并用PIL.Image.open打开图片,转换成RGB形式。
在调用的时候只需要:
train_loader = DataLoader(MyDataset(txt_path),batch_size=batch_size,shuffle=False,drop_last=True)for data in train_loader:img, label = data
示例:
https://zhuanlan.zhihu.com/p/105507334
这里有官方MNIST的例子,从这里里面,我们可以学到的是如何从拿到图片地址->下载图片->读取图片并加载到模型进行训练的一个完整过程。如果你有类似的任务,或者说像我当前这个项目一样,需要用到图片和文本的联合训练,那么也许可以把他们组织成一个样本,然后通过DataLoder每次返回,不仅返回图片,还有文本信息和标签信息,这样就可以直接进行下一步训练,并且不用关心图片和文本是否对应的上这样的问题。
参考:
tensor
tensor的一些操作
https://www.jianshu.com/p/314b6cfce1c3
tensor维度转换(拼接,新增,拆分)
维度拼接 torch.cat
a = torch.rand(4, 32, 8)b = torch.rand(5, 32, 8)c = torch.cat([a, b], dim=0) # 按第0维度进行拼接,除拼接之外的维度必须相同print(c.shape)# 结果:torch.Size([9, 32, 8])
产生一个新维度 torch.stack
a = torch.rand(5, 32, 8)b = torch.rand(5, 32, 8)c = torch.rand(5, 32, 8)d = torch.stack([a, b, c], dim=0) # 产生一个新的维度,待拼接的向量维度相同print(d.shape)# 结果:torch.Size([3, 5, 32, 8])
按指定长度拆分 split
a = torch.rand(6, 32, 8)b, c = a.split(3, dim=0) # 所给的是拆分后,每个向量的大小,指定拆分维度print(b.shape)print(c.shape)# 结果:# torch.Size([3, 32, 8])# torch.Size([3, 32, 8])
按给定数量拆分 chunk
a = torch.rand(6, 32, 8)b, c, d = a.chunk(3, dim=0) # 所给的是拆分的个数,即拆分成多少个print(b.shape)print(c.shape)# 结果:# torch.Size([2, 32, 8])# torch.Size([2, 32, 8])
替换tensor中大于某个值的所有元素
```python
import torch a = torch.rand(2,3) a tensor([[0.6976, 0.9647, 0.2891],
[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. 只保存参数# savetorch.save(model.state_dict(), path)# loadmodel.load_state_dict(torch.load(path))# 也可以同时保存其他信息state = {'model': model.state_dict(), 'optimizer': optimizer.state_dict(), 'epoch': epoch}torch.save(state, path)# loadcheckpoint = torch.load(path)model.load_state_dict(checkpoint['model'])optimizer.load_state_dict(checkpoint['optimizer'])epoch = checkpoint(['epoch'])# 2. 保存模型torch.save(model, './model.pkl')model = torch.load('./model.pkl')
深入理解
首先,torch.save是可以保存各种对象的,tensor,list,map,object等都可以
import torch.nn as nnclass MLP(nn.Module):def __init__(self):super(MLP, self).__init__()self.hidden = nn.Linear(3, 2)self.relu = nn.ReLU()self.output = nn.Linear(2, 1)def forward(self, x):x = self.hidden(x)x = self.relu(x)x = self.output(x)return xnet = MLP()print(net.state_dict())-- outputOrderedDict([('hidden.weight', tensor([[-0.3975, 0.2310, -0.1084], [ 0.0693, -0.3207, -0.3304]])),('hidden.bias', tensor([ 0.1656, -0.0796])),('output.weight', tensor([[-0.3187, 0.3556]])),('output.bias', tensor([0.4653]))])print(list(net.parameters()))-- output[Parameter containing: tensor([[-0.3975, 0.2310, -0.1084], [ 0.0693, -0.3207, -0.3304]], requires_grad=True),Parameter containing: tensor([ 0.1656, -0.0796], requires_grad=True),Parameter containing: tensor([[-0.3187, 0.3556]], requires_grad=True),Parameter containing: tensor([0.4653], requires_grad=True)]
- 对net.state_dict() 这是net.state_dict()是一个字典,且是一个有序字典,存储的是有参数的层。
- 对net.parameters()返回一个迭代器,存的也是可训练的参数,所以看起来,这俩没啥区别
# loadpretrained_dict = torch.load(log_dir) # 加载参数字典model_state_dict = model.state_dict() # 加载模型当前状态字典pretrained_dict_1 = {k:v for k,v in pretrained_dict.items() if k in model_state_dict} # 过滤出模型当前状态字典中没有的键值对model_state_dict.update(pretrained_dict_1) # 用筛选出的参数键值对更新model_state_dict变量model.load_state_dict(model_state_dict) # 将筛选出的参数键值对加载到模型当前状态字典中
从这里可以看出,理解了模型的参数保存,只是保存了一个字典以后,在加载的时候,就可以先加载参数字典,然后将参数字典更新给现在的模型,或者需要对原来保存的参数进行部分修改,也可以直接修改这里的pretrained_dict,然后加载给模型即可。
参考:
- 建议优先看官方文档:https://pytorch.org/tutorials/beginner/saving_loading_models.html
- https://www.jianshu.com/p/6c558300130f
- https://zhuanlan.zhihu.com/p/73893187
构建模型的方法
构建模型有两种方法:
- 采用class 方式定义Model,通过foward调用
- 采用torch.nn.Sequential快速搭建
示例:
# 普通方法class Net(torch.nn.Module):def __init__(self, n_feature, n_hidden, n_output):super(Net, self).__init__()self.hidden = torch.nn.Linear(n_feature, n_hidden)self.predict = torch.nn.Linear(n_hidden, n_output)def forward(self, x):x = F.relu(self.hidden(x))x = self.predict(x)return xnet1 = Net(1, 10, 1)# 快速方法net2 = torch.nn.Sequential(torch.nn.Linear(1, 10),torch.nn.ReLU(),torch.nn.Linear(10, 1))print(net1)"""Net ((hidden): Linear (1 -> 10)(predict): Linear (10 -> 1))"""print(net2)"""Sequential ((0): Linear (1 -> 10)(1): ReLU ()(2): Linear (10 -> 1))"""
为什么tensor要区分cpu和gpu?
原因主要是cpu张量的参数存在内存中,gpu张量存在显存中,当你在gpu中运行一个运算时,因为gpu只能计算存放在显存中的数据,因此你就需要将cpu张量转换为gpu张量,不然该张量就会找不到,所以cpu张量和gpu张量之间的转换,需要进行内存/显存交换。
另外一个场景就是,当你在用GPU进行训练,而运算结果需要存在列表中时,默认是会将结果保存在显存中,这有可能导致GPU训练超出内存,所以建议将运算结果得到的GPU张量转换为CPU张量,再存放在列表中。
下面通过一个小实验来看一下:
import torchdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')print("device:", device)a = torch.randn((3,1))b = torch.randn((3,1))c = a + bprint("a:", a.device)print("b:", b.device)print("c:", c.device)b = b.to(device)d = a + bprint("a:", a.device)print("b:", b.device)print("d:", d.device)-- outputdevice: cudaa: cpub: cpuc: cpuTraceback (most recent call last):File "ca87f0b1-1171-4453-9e39-ad981e327e12_test.py", line 17, in <module>d = a + bRuntimeError: expected type torch.FloatTensor but got torch.cuda.FloatTensor
这里就看的很明确了,首先,默认的tensor是建立在cpu上的,当明确指定转换为gpu上的时(13行),此时再进行cpu和gpu上tensor的混合运算就会报错,此时就必须统一转换为cpu或gpu上的tensor。
