目标

  • 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提供,分别是torchvisiontorchtext 其中:

  • 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)

安装torch加镜像源_数据

其中,安装torch加镜像源_pytorch_02

安装torch加镜像源_pytorch_03

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)