编辑
2022-12-26
pytorch
00
请注意,本文编写于 851 天前,最后修改于 182 天前,其中某些信息可能已经过时。

目录

pytorch-手写数字识别
0.0 每日code
1. 模型中使用数据加载器的目的
2. 数据集类
3. 迭代数据集
4. pytorch 自带的数据集
4.1 torchversion.datasets
5. pytorch-mnist 手写数字识别
5.1 torchvision.transforms. ToTensor
5.2 torchvision. transforms. Normalize(mean, std)
5.1.3 torchvision.transforms.Compose(transforms)
6. 准备mnist数据集 Dataset & Dataloader
7. 构建模型
7.1 激活函数的使用
7.2 模型中数据的形状
7.3 模型损失函数
4.模型的训练
5.模型的保存和加载

pytorch-手写数字识别

使用pytorch框架实现手写数字识别的功能

0.0 每日code

python
import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader from torch.optim import Adam from torchvision.datasets import MNIST from torchvision.transforms import Compose,ToTensor,Normalize BATCH_SIZE = 128 #1. prepare the dataset def get_dataloader(train=True) : transform_fn = Compose([ ToTensor(), Normalize(mean = (0.1307,) , std = (0.3081 , ) ) # the shape of mean and std is the same as the number of channel ]) dataset= MNIST(root = "./mnist_data" , train =train , transform = transform_fn ) data_loader = DataLoader(dataset,batch_size=BATCH_SIZE, shuffle = True) return data_loader class MnistModel(nn.Module): def __init__(self): super(MnistModel,self).__init__() self.fc1 = nn.Linear(1*28*28, 28)# fc1 全连接的意思 self.fc2 = nn.Linear(28 , 10) def forward(self,input): x = input.view([input.size(0),1*28*28]) x = self.fc1(x) # 矩阵乘法+偏置 2. 进行全连接的操作 x = F.relu(x) # 3. 进行激活函数的处理,形状没有变化 out = self.fc2(x) # 4. 输出层 return F.log_softmax(out) def train(epoch): # 实现训练的过程 model = MnistModel() optimizer = Adam(model.parameters(),lr=0.001) data_loader = get_dataloader() for idx,(input,target)in enumerate(data_loader): optimizer.zero_grad() output = model(input) #调整模型,得到预测值 loss = F.nll_loss(output,target) # 得到损失值 optimizer.step() # 梯度的更新 if idx%100 == 0 : # 没100次更新一次他的loss print(loss.item()) if idx%100 == 0 if __name__ == '__main__': for i in range(3): # 3 times train(i)

1. 模型中使用数据加载器的目的

在前面的线性回归模型中,我们使用的数据很少,所以直接把全部数据放到模型中去使用。但是在深度学习中,数据量通常是都非常多,非常大的,如此大量的数据,不可能一次性的在模型中进行向前的计算和反向传播,经常我们会对整个数据进行随机的打乱顺序,把数据处理成一个个
batch,同时还会对数进行预处, 所以,接下来我们来学习pytorch中的数据加载的方法

2. 数据集类

需要继承 在torch中提供数据集的基类 **torch.utils.data.Dataset**
python
class Dataset(object): def __getitem__(self,index): raise NotImplementedError def __len__(self): raise NotImplementedError def __add__(self,other): raise ConcatDataset([self,other])

由上已知,我们需要在自定义的数据集类中继承Dataset 类,同时还需要实现两个方法:

  1. __len__方法 : 通过全局的len()方法获取其中元素的个数
  2. __getitem__方法 : 通过传入索引的方式获取数据集,例如通过dataset[ i ] 获取其中的第 i 条数据
python
import torch # import torch.utils.data.Dataset # import Dataset for torch.utils.data from torch.utils.data import Dataset data_path = r"./Spam-classification/SMSSpamCollection.txt" # 完成数据集类 class MyDataset(Dataset): def __init__(self): self.lines = open(data_path).readlines() def __getitem__(self,index): # 获取对应索引的第一条数据 # return self.lines[index] return self.lines[index].strip() # 消去换行 def __len__(self): return len(self.lines) if __name__ == '__main__': my_dataset = MyDataset() # 实例化一个对象 print(my_dataset[1000]) # 打印第一行 print(len(my_dataset))

之后对dataset进行实例化,通过迭代获取其中数据

python
# 打印出部分数据集 for i in range(len(my_dataset)): print(i,my_dataset[i])

image

3. 迭代数据集

目标: 为了实现读取 txt 文件的标签以及其内容,优化 -getitem-函数

python
def __getitem__(self,index): # 获取对应索引的第一条数据 cur_line = self.lines[index].strip() # 获取当前行 label = cur_line[:4].strip() content = cur_line[4:].strip() return label,content

image

使用上述的方法能够进行数据的读取,但是其中还有很多内容没有实现:

  1. 批处理数据(Batching the data)
  2. 打乱数据(Shuffling the data)
  3. 使用多线程multiprocessing并行加载数据。

在pytorch中 torch.utils.data.DataLoader提供了上述的所用方法

其中参数含义:

  1. dataset: 提前定义的dataset的实例
  2. batch_size: 传入数据的batch的大小,常用128,256等等
  3. shuffle: bool类型,表示是否在每次获取数据的时候提前打乱数据
  4. num_workers: 加载数据的线程数
  5. drop_last : 当最后一个batch里面的个数不足够,则直接删除
python
from torch.utils.data import Dataset ,DataLoader # 完成数据集类 class MyDataset(Dataset): def __init__(self): self.lines = open(data_path).readlines() def __getitem__(self,index): # 获取对应索引的第一条数据 # return self.lines[index].strip() # 消去换行 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) my_dataset = MyDataset() # 实例化一个对象 data_loader = DataLoader(dataset = my_dataset , batch_size = 3 ,shuffle = True,drop_last = True ) if __name__ == '__main__': for i in data_loader: print(i) break

不同的 batch_size 结果不一样:

*batch = 3 *

image

*batch = 10 *

image

同时 由于shuffle 为 true 所以每一次迭代都会刷新当前的batch内容

基于普通的便利之后,通常遍历参数: index --- 使用 enumerate方式遍历,则优化代码:

python
for index , (label ,context ) in enumerate(data_loader): print( index , (label ,context) ) print("*"*100)
the result :![image](assets/image-20221217171621-thcqc5t.png)​

notes:

  1. len(dataset) = 数据集的样本数量
  2. len(dataloader) = math.ceil( 样本数/ batch_size ) 向上取整

4. pytorch 自带的数据集

pytorch中自带的数据集由两个上层api提供,分别是torchvisiontorchtext:

  1. torchvision ** 提供了对图片**数据处理相关的api和数据
    数位置: torchvision. datasets, 例: torchvision.datasets. MNIST (手写数
    字图片数据)

  2. **torchtext ** 提供了对文本数据处理相关的API和数据
    数据位置: torchtext.datasets,例如: torchtext.datasets.IMDB (电影评论文本数据)
    下面我们以Mnist手写数字为例,来看看pytorch如何加截其中自带的数据集
    使用方法和之前一样:

    1. 准备好Dataset实例
    2. dataset交给dataloader 打乱顺序,组成batch
4.1 torchversion.datasets

torchversoin.datasets 中的数据集类(比 torchvision. datasets.MNIST) ,都是继承自
Dataset. 意味着: 直接对torchvision.datasets,MNIST 进行实例化就可以得到. Dataset的实例, 但是MNIST API中的参数需要注意一下:

python
torchvision.datasets.MNIST(root='/files/', train=True, download=True, transform=)
  1. root参数表示数据存放的位置
  2. train: bool类型,表示是使用训练集的数据还是测试集的数据
  3. download: bool类型,表示是否需要下载数据到root日录
  4. transform: 实现的 对图片的处理函数
python
import torchvision from torchvision.datasets import MNIST mnist = MNIST(root = "./mnist_data",train = True , download = True ) print(mnist)

image

5. pytorch-mnist 手写数字识别

作业目标:

  1. 知道如何使用Pytorch完成神经网络的构建
  2. 知道Pytorch中激活函数的使用方法
  3. 知道Pytorch中torchvision.transforms中常见图形处理函数的使用
  4. 知道如何训练模型和如何评估模型
5.1 torchvision.transforms. ToTensor
把一个取值范围 [0,255]的 PIL.Image \ shape ( H,W,C ) 的 numpy.ndarray ,转换成为形状 [C , H , W ] 取值范围是 [ 0 , 1.0 ] 的 torch. FloatTensor 其中 ( H , W ,C ) == (**高、宽、**​***通道数*** * * ) ,黑白通道数只有1 ,其中每个像索点的取值为 [0,255] , 彩色图片的通道数为(R,G,B), 每个通道的每个像素点的取值 [0,255],三个通道的颜色相互叠加,形成了各种颜色
python
from torchvision import transforms import numpy as np data = np.random.randint(0,255,size=12) img = data.reshape(2,2,3) print(img.shape) img_tensor = transforms.ToTensor()(img) # 先实例化然后传入图片img 然后转化为tensor print(img_tensor) print(img_tensor.shape) ''' # way-2 img_tensor = torch.tensor(img) img_tensor.transpose(0,2) # 交换通道,交换0 2 通道 img_tensor.permute(2,0,1) # permute函数的作用是对tensor进行转置。 '''

image

5.2 torchvision. transforms. Normalize(mean, std)

给定均值: mean, shape和图片的通道数相同(指的是每个通道的均值),方差

,和图片的通道数相同(指的是每个通道的方差),将会把Tensor​规范化处理。
即: Normalized_image=(image-mean)/std

i. 均值和标准差的形状和通道数相同

5.1.3 torchvision.transforms.Compose(transforms)

将多个transform组合起来使用。

i. 传入list

ii.
例如

python
transforms.Compose([ torchvision.transforms.ToTensor(), # 先转化为 Tensor torchvision.transforms.Normalize(mean,std) # 再进行正则化 ])

6. 准备mnist数据集 Dataset & Dataloader

准备训练集合:

python
import torchvision from torch.utils.data import DataLoader import matplotlib.pyplot as plt from torchvision.datasets import MNIST from torchvision.transforms import Compose,ToTensor,Normalize BATCH_SIZE = 128 # 准备数据迭代器 def get_dataloader(train=True): transform_fn = Compose([ ToTensor(), Normalize(mean=(0.1307,),std=(0.381,)) ]) dataset = MNIST(root="./mnist_data",train=True,transform = transform_fn) data_loader = DataLoader(dataset , batch_size=BATCH_SIZE , shuffle = True) return data_loader

7. 构建模型

补充:

全联接层 : 当前一层的神经元和前一层的神经网络相互链接,其核心操作就是 y=wx , 即 矩阵的乘法,实现对前一层数据的变换

模型的构建使用了一个四层的神经网络,其中包括两个全联接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果,那么这个模型有两个点需要注意:

  1. 激活函数的使用

  2. 每一层数据的形状

  3. 模型的损失函数

7.1 激活函数的使用

常见函数: Relu 激活函数 :

import torch.nn.functional as F 提供, F.relux(x) 即可对

python
import torch from torch import nn import torch.nn.functional as F class MnistNet(nn.Module): def __init__(self): super(MnistNet,self).__init__() self.fc1 = nn.Linear(28*28*1,28) # 定义Linear的输入输出形状 self.fc2 = nn.Linear(28,10). # 定义Linear 的输入和输出的形状 def forwoard(self,input ): ''' # : param input : [batch_size , 1 , 28, 28 ] # : return : ''' x = x.view(-1,28*28*1) #对数据形状变形,-1 表示该位置根据后面的形状自动调整 x = self.fc(x) #[batch_size,28] x = F.relu(x). #[batch_size,28] x = self.fc2(x) # [batch_size,10]

7.2 模型中数据的形状

'''
batch_size 形状不可以改变、

  1. 原始输入数据的形状 :[batch_size , 1, 28, 28 ]
  2. 进行形状修改 , [batch_size, 28*28 ]
  3. 第一个全连接输出的形状: [batch_size, 28] ,
  4. 激活函数不会改变数据的形状
  5. 第二个全连接层输出的形状:[batchsize,10][batchsize,10], 因为手写数字共有10个类别
    '''

python
import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader from torch.optim import Adam from torchvision.datasets import MNIST from torchvison.transforms import Compose,ToTensor,Normalize BATCH_SIZE = 128 #1. prepare the dataset def get_dataloader(train=True) : transform_fn = Compose([ ToTensor(), Normalize(mean = (0.1307,) , std = (0.3081 , ) ) # the shape of mean and std is the same as the number of channel ]) dataset= MNIST(root = "./data" , train =train , transform = transform_fn ) data_loader = DataLoader(dataset,batch_size=BATCH_SIZE, shuffle = True) return data_loader class MnistModel(nn.Module): def __init__(self): super(MnistModel,self).__init__() self.fc1 = nn.Linear(1*28*28, 28)# fc1 全连接的意思 self.fc2 = nn.Linear(28 , 10) def forward(self,input): # : param input : [batch_size, 1,28,28] # : return; # 1. 修改形状 # input.view([batch_size, 1*28*28]) 通常不直接写 batc_size ,因为可能不能整除 x = input.view([input.size(0), 1*28*28]) # input.view([-1,1*28*28]) # # 2. 进行全连接的操作 x = self.fc1(x) # 矩阵乘法+偏置 # 3. 进行激活函数的处理,形状没有变化 x = F.relu(x) # 4. 输出层 out = self.fc2(x) return F.lo_softmax(out) def train(epoch): # 实现训练的过程 model = MnistModel() optimizer = Adam(model.parameters(),lr=0.001) data_loader = get_dataloader() for idx,(input,target)in enumerate(data_loader): optimizer.zero_grad() output = model(input) #调整模型,得到预测值 loss = F.nll_loss(output,targrt) # 得到损失值 optimizer.step() # 梯度的更新 if idx%100 == 0 : print(loss.item()) if __name__ == '__main__': for i in range(3): # 3 times train(i)

7.3 模型损失函数

首先,我们需要明确,我们手写字体识别的问题是一个多分类问题。

逻辑回归中,我们使用sigmoid进行计算对数似然损失来定义我们的2分类的损失

  • 在二分类中,我们有正类和负类的概率为: P(X)=11+ex=ex1+ex,P(X) = { \frac { 1 }{ 1 + e ^ { -x } } } = \frac{ e^{x} }{ 1+e^{x} }, 那么负类的概率为: 1P(X)1-P(X)

    • 将这个结果进行**对数似然损失 **

      ylog(P(x))-\sum{y log(P(x))}

      可以求得最终的损失。

那么在多分类中:

区别在于不能再使用sigmoid函数来计算当前样本属于某个类别的概率,而应该使用softmax() 函数

  • softmax 和 sigmoid 的区别在于我们需要计算的样本属于每个类别的概率,需要多次计算,而sigmoid只需要计算一次:

    • softmax公式:

      σ(z)j=ezjk=1nezk\sigma(z)_j = \frac{e^{z_j}}{\sum_{k=1}^{n} e^{z_k}}

    k 表示为几维度

image

对于这个`softmax`​输出的结果,是在 [**0,1]** 区间,我们可以把它当做概率和前面2分类的损失一样,多分类的损失只需要再把这个结果进行对数似然损失的计算即可,即 :
J=Ylog(P),其中P=ezjk=1KezkJ = - \sum{Y log(P) }, 其中 P = \frac{e^{z_j}}{\sum_{k=1}^K e^{z_k}}

最后,会计算每个样本的损失,即上式的平均值. 我们把softmax概率传入对数似然损失得到的损失函数称为 **交叉熵损失. **

在pytorch中有两种方法实现交叉熵损失:

python
criterion = nn.CrossEntropyLoss() loss = criterion(input,target) # 实例化对像,传入input & target
python
#1. 对输出值计算softmax和取对数 output = F.log_softmax(x,dim=-1) # 2. 使用torch中的带权损失 loss = F.nll_loss(output,target)

带权损失定义为:

ln=wixil_n = - \sum w_i x_i

其中就是把log(p)log(p) 作为 xix_i,把真实数值 YY作为权重

4.模型的训练

训练的流程:
1.实例化模型,设置模型为训练模式
2.实例化优化器类,实例化损失函数
3.获取,遍dataloader4,梯度为0
5.进行向前计算
6.计算损失
7,反向传播
8.更新参数

python
mnist_net = MnistNet() optimizer = optim.Adam(mnist_net.parameters(),lr=0.001) def train(epoch): mode = True mnist_net.train(mode = mode ) # 模型设置为训练模型 train_dataloader = get_dataloader(train=mode) # 获取训练数据集 for idx,(data,target) in enumerate(train_dataloader): optimizer.zero_grad() # 梯度设定为0 output

5.模型的保存和加载

python
for idx%100 == 0 # torch.save(mnist_net.state_dict(), "" )

本文作者:wenY

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!