目标
- 1.知道数据加载的目的
- 2.知道pytorch中Dataset的使用方法
- 3.知道pytorch中DataLoader的使用方法
- 4.知道pytorch中自带数据集如何获取
1.使用数据加载器的目的
深度学习是由数据支撑起来的,所以我们一般在做深度学习的时候往往伴随着大量、复杂的数据。如果把所有的数据全部加载到内存上,容易把电脑的内存“撑爆”,所以要分批次一点点加载数据。
每一种深度学习的框架都有自己所规定的数据格式,数据加载器就有了必要的作用。
数据加载器就是把大量的数据,分批次加载和处理成框架所需要的数据格式。
2.数据集类
2.1 Dataset基类介绍
在torch中提供了数据集的基类torch.utils.data.Dataset
,其源码如下:
class Dataset(object):
"""An abstract class representing a :class:`Dataset`.
All datasets that represent a map from keys to data samples should subclass
it. All subclasses should overwrite :meth:`__getitem__`, supporting fetching a
data sample for a given key. Subclasses could also optionally overwrite
:meth:`__len__`, which is expected to return the size of the dataset by many
:class:`~torch.utils.data.Sampler` implementations and the default options
of :class:`~torch.utils.data.DataLoader`.
.. note::
:class:`~torch.utils.data.DataLoader` by default constructs a index
sampler that yields integral indices. To make it work with a map-style
dataset with non-integral indices/keys, a custom sampler must be provided.
"""
def __init__(self, data_root, pad_size, flod_num, training=True):
#必须进行重写,依据索引获取数据的值
def __getitem__(self, index):
raise NotImplementedError
#必须进行重写,获取数据集数据的个数
def __len__(self):
return NotImplementedError
#进行数据集的合并,不常用,一般不需要进行重写
def _add_(self,other)
return ConcatDataset([self,other])
2.2 数据加载的实际操作
import torch
from torch.utils.data import Dataset
data_path=r"D:\NLP学习\第三天.zip..."#在这里添加路径名称,数据集在电脑压缩文件内存储,不展示路径
class MyDataset(Dataset):
def _init_(self):
self.lines=open(data_path).readlines()
def _getitem_(self,index):
cur_line=self.lines[index].strip()
label=cur_line[:4].strip()#提取标签
content=cur_line[4:].strip()#提取数据
return label,content#实现标签和数据的分离
def _len_(self):
return len(self.lines)
if _name_=='_main_':
my_dataset=MyDataset()#实例化
#以下是一些基本操作:
print(my_dataset[0])
print(len(my_dataset))
for i in range(len(my_dataset)):
print(i,my_dataset[i])
3. 迭代数据集
使用上述方法能够进行数据的提取,但是我们还要实现:
- 批处理数据(Batching the data)
- 打乱数据(Shuffling the data)
- 使用多线程
multiprocessing
并行加载数据
在pytorch中torch.utils.data DataLoader
提供了上述的所用方法,一个实例如下:
from torch.utils.data import DataLoader
dataset=CifarDataset()#实例化
data_loader=
DataLoader(dataset=dataset,batch_size=10,shuffle=True,num_worker=2)
上述实例中只使用了常见参数,其中参数含义如下:
- 1.dataset:提前定义的dataset的实例。
- 2.batch_size:传入数据的batch的大小,常用128,256等等。
- 3.shuffle:bool类型,表示是否在每次获取数据的时候打乱数据。
- 4.
num_workers:
加载数据的并行线程数量。一般来说,线程数量的多少与运行速度并没有太大的关系。
此外,值得注意的一点是:len(data_loader)=[len(dataset)]+1
,这是由于:规定了batch的数量后,就相当于自定义的一个数据集,从Dataset中不断取出数据放入batch,因此有上述的数学关系式,即上取整加1。
4. pytorch自带的数据集
pytorch中自带的数据集有两个上层的API提供,分别是torchvision
和torchtext
其中:
- 1.
torchvision
提供了对图片数据处理相关的API和数据 - 数据位置:
torchvision.datasets
,例如:torchvision.datasets.MNIST
(手写数字图片数据) - 2.
torchtext
提供了对文本数据处理相关的API和数据 - 数据位置:
torchtext.datasets
,例如:torchtext.datasets.IMDB
(电影评论文本数据)
下面以Mnist手写数字为例,使用方法:
- 1.实例化Dataset。
- 2.把dataset交给dataloader处理,组成batch。
4.1 torchvision.datasets
MNIST API中的参数需要注意一下:torchvision.datasets.MNIST(root='/files/',train=True,download=True,transform=)
- 1.root:表示数据存放的位置。
- 2.train:bool类型,表示使用的是训练集还是测试集。
- 3.download:bool类型,表示是否需要下载数据到root目录。
- 4.transform:实现对图片的处理函数
4.2 torchvision.transforms
torchvision.transforms
是pytorch中的图像预处理包,包含了很多种对图像数据进行变换的函数,这些都是在我们进行图像数据读入步骤中必不可少的。
4.2.1 torchvision.transforms中的基本函数
torchvision.transforms.ToTensor :
把一个取值范围是[0,255]的PIL.Image
或者shape为(H,W,C)的numpy.ndarray
,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloadTensor
。
其中,[H,W,C]代表[高,宽,通道数],黑白图片的通道数为1,彩色图片的通道数为3,每个像素点的取值为[0,255]。
torchvision.transforms.Normalize(mean, std):
用给定的均值和标准差分别对每个通道的数据进行正则化。具体来说,给定均值(M1,…,Mn),给定标准差(S1,…,Sn),其中n是通道数(需要一一对应),对每个通道进行如下操作:
output[channel] = (input[channel] - mean[channel]) / std[channel]
比如:原来的tensor是三个维度的,值在[0,1]之间,经过变换之后就到了[-1,1]
计算如下:((0,1)-0.5)/0.5=(-1,1)
torchvision.transforms.ToPILImage
:将shape为(C,H,W)的Tensor
或shape为(H,W,C)的numpy.ndarray
转换成PIL.Image
,值不变。
torchvision.transforms.CenterCrop(size):
将给定的PIL.Image
进行中心切割,得到给定的size,size可以是tuple,(target_height, target_width)。size也可以是一个Integer,在这种情况下,切出来的图片的形状是正方形。
torchvision.transforms.RandomCrop(size, padding=0):
切割中心点的位置随机选取。size可以是tuple也可以是Integer。
torchvision.transforms.RandomHorizontalFlip:
随机水平翻转给定的PIL.Image
,概率为0.5。即:一半的概率翻转,一半的概率不翻转。
torchvision.transforms.RandomSizedCrop(size, interpolation=2):
先将给定的PIL.Image
随机切,然后再resize成给定的size大小。
torchvision.transforms.Pad(padding, fill=0):
将给定的PIL.Image
的所有边用给定的pad value填充。 padding:
要填充多少像素;fill:
用什么值填充。
4.2.2 torchvision.transforms.Compose
transforms.Compose(transforms) 方法是将多种变换组合在一起,例如:
data_transforms = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
上述对data_transforms进行了四种变换,前两个是对PILImage进行的,分别对其进行随机大小和随机宽高比的裁剪,之后resize到指定大小224,以及对原始图像进行随机的水平翻转;
第三个transforms.ToTensor() 将PILImage转变为torch.FloatTensor的数据形式;
而最后一个Normalize则是对tensor进行的正则化。
多种组合变换有一定的先后顺序,处理PILImage的变换方法(大多数方法)都需要放在ToTensor方法之前,而处理tensor的方法(比如Normalize方法)就要放在ToTensor方法之后。
4.3 构建模型
模型的构建使用了以一个四层的神经网络,其中包括两个全连接层和一个输出层,第一个全连接层经过激活函数的处理,将处理结果交给下一个全连接层,进行变换后输出结果。
在这个模型中,需要注意:
- 1.激活函数如何使用
- 2.每一层数据的形状
- 3.模型的损失函数
4.3.1 激活函数
激活函数有很多,常见的有Relu激活函数。
4.3.2 模型中数据的形状
- 1.原始数据的形状:[batch_size,1,28,28]
- 2.进行形状的修改:[batch_size,28*28]
- 3.第一个全连接层的输出形状:[batch_size,28],这里的28是人为设定的
- 4.激活函数不会修改数据的形状
- 5.第二个全连接层的输出形状:[batch_size,10]
在上面,batch_size一直保持不变,这是由DataLoader中每一次加载的数据个数所决定的。
补充:全连接层一般位于整个神经网络的最后(可以有多个),目的是将n-1层的i个数据转换成n层所需要的j个数据。因此传入的参数包括两个:n-1层的数据个数i,和n层的目标分类数量j。
4.3.3 模型的损失函数
交叉熵损失函数,详见:[link]https://blog.csdn.net/songgu1996/article/details/99056721?ops_request_misc=&request_id=&biz_id=102&utm_term=%E4%BA%A4%E5%8F%89%E7%86%B5%E6%8D%9F%E5%A4%B1%E5%87%BD%E6%95%B0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-2-99056721.142v42pc_rank_34,185v2control&spm=1018.2226.3001.4187
在pytorch中,有两种方法实现交叉熵损失:
#方法一:
criterion=nn.CrossEntropyLoss()
loss=criterion(input,target)
#方法二:
output=F.log_softmax(x,dim=-1)
loss=F.nll_loss(output,target)
其中,
4.3.4 模型的训练
训练的流程:
- 1.实例化模型,设置模型为训练模式
- 2.实例化优化器类,实例化损失函数
- 3.获取,便利downloader
- 4.梯度设置为0
- 5.进行向前计算
- 6.计算损失
- 7.反向传播
- 8.更新参数
4.4 模型的保存
4.4.1 模型的保存
一般来说,保存的对象为模型参数以及优化器参数:
torch.save(model.state_dict(),"./model/model.pkl")#需要新建一个model文件夹
torch.save(optimizer.state_dict(), "./model/optimizer.pkl")
4.4.2 模型的加载
加载方式实现:
import os
if os.path.exists("./model/model.pkl"):
model.load_state_dict(torch.load("./model/model.pkl"))
optimizer.load_state_dict(torch.load("./model/optimizer.pkl"))
4.5 模型的评估
在这一部分,我们需要依据之前构建好的模型,针对训练集进行评估。评估的过程和训练的过程相似,但是:
- 1.不需要进行梯度的计算
- 2.需要收集损失和准确率,用来计算平均损失和平均准确率
- 3.损失的计算和训练时候损失的计算方法相同
- 4.准确率的计算:
- 最大值的位置获取的方法可以使用
torch.max
,返回最大值和最大值的位置
4.6 完整代码
针对上述的手写数字的识别,完整代码如下:
from torchvision.datasets import MNIST
from torchvision.transforms import Compose,ToTensor,Normalize
from torch.utils.data import DataLoader
import torch
from torch import nn
from torch.optim import Adam
import torch.nn.functional as F
import numpy as np
import os
BATCH_SIZE=128
#1.准备数据集
def get_dataloader(train=True):#测试用函数
transform_fn=Compose([
ToTensor(),
Normalize(mean=(0.1307,),std=(0.3081)) #mean和std的形状应该和通道数量保持一致
])
dataset=MNIST(root="./data",train=True,transform=transform_fn)#实例化数据集
data_loader=DataLoader(dataset,batch_size=BATCH_SIZE,shuffle=True)#实例化迭代数据集
return data_loader
#2.构建模型
class MnistNet(nn.Module):
def __init__(self):
super(MnistNet,self).__init__()
self.fc1=nn.Linear(28*28*1,28)#第一个全连接层,将28*28个数据转换成28个
self.fc2=nn.Linear(28,10)#第二个全连接层,将上一个全连接层得到的28个数据转换成最后所需要的10个数据
def forward(self,x):#x的形状为:[batch_size,1,28,28]
#1.修改形状
x=x.view(-1,28*28*1)#对数据形状进行改变,-1表示该位置根据后面的形状自动调整
#2.进行全连接的操作
x=self.fc1(x)
#3.进行激活函数的处理,形状没有变化
x=F.relu(x)
#4.输出层
out=self.fc2(x)
return F.log_softmax(out,dim=-1)#dim确定维度,dim=-1表示在最后一个维度
model = MnistNet()
optimizer = Adam(model.parameters(), lr=0.001)
#如果路径存在:
if os.path.exists("./model/model.pkl"):
model.load_state_dict(torch.load("./model/model.pkl"))
optimizer.load_state_dict(torch.load("./model/optimizer.pkl"))
#模型的训练
def train(epoch):
"""实现训练的过程"""
data_loader=get_dataloader()
for idx,(input,target) in enumerate(data_loader):
optimizer.zero_grad()
output=model(input)#调用模型,得到预测值
loss=F.nll_loss(output,target)#得到损失
loss.backward()#反向传播
optimizer.step()#梯度的更新
if idx%10 ==0:
print(epoch,idx,loss.item())#轮数,索引,损失率
#模型的保存
if idx%100==0:
torch.save(model.state_dict(),"./model/model.pkl")#需要新建一个model文件夹
torch.save(optimizer.state_dict(), "./model/optimizer.pkl")
#模型的测试
def test():
test_dataloader=get_dataloader(train=False,batch_size=BATCH_SIZE)
loss_list=[]
acc_list=[]
for idx,(input,target) in enumerate(test_dataloader):
with torch.no_grad():
output=model(input)
cur_loss=F.nll_loss(output,target)
loss_list.append(cur_loss)
# 计算准确率
# output的形状为:[batch_size,10],target的形状为:[batch_size]
pred=output.max(dim=-1)[-1]#获取预测值
cur_acc=pred.eq(target).float.mean()#eq()用于判断是否相等,返回值为bool类型,故通过mean()可以直接求准确率
acc_list.append(cur_acc)
print("平均准确率,平均损失:",np.mean(acc_list),np.mean(loss_list))
if __name__ =='__main__':
for i in range(3):#训练3轮
train(i)