读取数据 DataLoader
pytorch读数据,目前来看,DataLoader是比较常用的一种方式,采用这种方法有3步:
- 创建一个DataSet对象,继承自dataset类
- 创建一个DataLoader对象
- 从这个DataLoader对象中,循环读取数据
按我目前的理解,首先我们需要把全量数据在自己定义的DataSet类中,定义好每次迭代的方法,然后就用DataLoader进行加载即可。可以把所有的数据都放到DataSet类里进行处理,保证DataLoader返回的就是直接可以输入模型训练的样本。
示例:
# coding: utf-8
from PIL import Image
from torch.utils.data import Dataset
class 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 = imgs
self.transform = transform
self.target_transform = target_transform
def __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, label
def __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. 只保存参数
# save
torch.save(model.state_dict(), path)
# load
model.load_state_dict(torch.load(path))
# 也可以同时保存其他信息
state = {'model': model.state_dict(), 'optimizer': optimizer.state_dict(), 'epoch': epoch}
torch.save(state, path)
# load
checkpoint = 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 nn
class 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 x
net = MLP()
print(net.state_dict())
-- output
OrderedDict([('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()返回一个迭代器,存的也是可训练的参数,所以看起来,这俩没啥区别
# load
pretrained_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 x
net1 = 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 torch
device = 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 + b
print("a:", a.device)
print("b:", b.device)
print("c:", c.device)
b = b.to(device)
d = a + b
print("a:", a.device)
print("b:", b.device)
print("d:", d.device)
-- output
device: cuda
a: cpu
b: cpu
c: cpu
Traceback (most recent call last):
File "ca87f0b1-1171-4453-9e39-ad981e327e12_test.py", line 17, in <module>
d = a + b
RuntimeError: expected type torch.FloatTensor but got torch.cuda.FloatTensor
这里就看的很明确了,首先,默认的tensor是建立在cpu上的,当明确指定转换为gpu上的时(13行),此时再进行cpu和gpu上tensor的混合运算就会报错,此时就必须统一转换为cpu或gpu上的tensor。