使用pytorch框架实现手写数字识别的功能
pythonimport 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)
在前面的线性回归模型中,我们使用的数据很少,所以直接把全部数据放到模型中去使用。但是在深度学习中,数据量通常是都非常多,非常大的,如此大量的数据,不可能一次性的在模型中进行向前的计算和反向传播,经常我们会对整个数据进行随机的打乱顺序,把数据处理成一个个
batch,同时还会对数进行预处, 所以,接下来我们来学习pytorch中的数据加载的方法
需要继承 在torch中提供数据集的基类 **torch.utils.data.Dataset**
pythonclass Dataset(object):
def __getitem__(self,index):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
def __add__(self,other):
raise ConcatDataset([self,other])
由上已知,我们需要在自定义的数据集类中继承Dataset 类,同时还需要实现两个方法:
pythonimport 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])
目标: 为了实现读取 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
使用上述的方法能够进行数据的读取,但是其中还有很多内容没有实现:
在pytorch中 torch.utils.data.DataLoader提供了上述的所用方法
其中参数含义:
- dataset: 提前定义的dataset的实例
- batch_size: 传入数据的batch的大小,常用128,256等等
- shuffle: bool类型,表示是否在每次获取数据的时候提前打乱数据
- num_workers: 加载数据的线程数
- drop_last : 当最后一个batch里面的个数不足够,则直接删除
pythonfrom 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 *
*batch = 10 *
同时 由于shuffle 为 true 所以每一次迭代都会刷新当前的batch内容
基于普通的便利之后,通常遍历参数: index --- 使用 enumerate方式遍历,则优化代码:
python for index , (label ,context ) in enumerate(data_loader):
print( index , (label ,context) )
print("*"*100)
the result :
notes:
- len(dataset) = 数据集的样本数量
- len(dataloader) = math.ceil( 样本数/ batch_size ) 向上取整
pytorch中自带的数据集由两个上层api提供,分别是torchvision和torchtext:
torchvision ** 提供了对图片**数据处理相关的api和数据
数位置: torchvision. datasets, 例: torchvision.datasets. MNIST (手写数
字图片数据)
**torchtext ** 提供了对文本数据处理相关的API和数据
数据位置: torchtext.datasets,例如: torchtext.datasets.IMDB (电影评论文本数据)
下面我们以Mnist手写数字为例,来看看pytorch如何加截其中自带的数据集
使用方法和之前一样:
- 准备好Dataset实例
- dataset交给dataloader 打乱顺序,组成batch
torchversoin.datasets 中的数据集类(比 torchvision. datasets.MNIST) ,都是继承自
Dataset. 意味着: 直接对torchvision.datasets,MNIST 进行实例化就可以得到. Dataset的实例, 但是MNIST API中的参数需要注意一下:
python torchvision.datasets.MNIST(root='/files/', train=True, download=True,
transform=)
pythonimport torchvision
from torchvision.datasets import MNIST
mnist = MNIST(root = "./mnist_data",train = True , download = True )
print(mnist)
作业目标:
- 知道如何使用Pytorch完成神经网络的构建
- 知道Pytorch中激活函数的使用方法
- 知道Pytorch中torchvision.transforms中常见图形处理函数的使用
- 知道如何训练模型和如何评估模型
把一个取值范围 [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],三个通道的颜色相互叠加,形成了各种颜色
pythonfrom 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进行转置。
'''
给定均值: mean, shape和图片的通道数相同(指的是每个通道的均值),方差
,和图片的通道数相同(指的是每个通道的方差),将会把Tensor
规范化处理。Normalized_image=(image-mean)/std
i. 均值和标准差的形状和通道数相同
将多个transform组合起来使用。
i. 传入list
ii.
例如
pythontransforms.Compose([
torchvision.transforms.ToTensor(), # 先转化为 Tensor
torchvision.transforms.Normalize(mean,std) # 再进行正则化
])
准备训练集合:
pythonimport 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
补充:
全联接层 : 当前一层的神经元和前一层的神经网络相互链接,其核心操作就是 y=wx , 即 矩阵的乘法,实现对前一层数据的变换
模型的构建使用了一个四层的神经网络,其中包括两个全联接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果,那么这个模型有两个点需要注意:
激活函数的使用
每一层数据的形状
模型的损失函数
常见函数: Relu 激活函数 :
import torch.nn.functional as F 提供, F.relux(x) 即可对
pythonimport 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]
'''
batch_size 形状不可以改变、
pythonimport 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)
首先,我们需要明确,我们手写字体识别的问题是一个多分类问题。
逻辑回归中,我们使用sigmoid进行计算对数似然损失来定义我们的2分类的损失
在二分类中,我们有正类和负类的概率为: 那么负类的概率为:
将这个结果进行**对数似然损失 **
可以求得最终的损失。
那么在多分类中:
区别在于不能再使用sigmoid函数来计算当前样本属于某个类别的概率,而应该使用softmax() 函数
softmax 和 sigmoid 的区别在于我们需要计算的样本属于每个类别的概率,需要多次计算,而sigmoid只需要计算一次:
softmax公式:
k 表示为几维度
对于这个`softmax`输出的结果,是在 [**0,1]** 区间,我们可以把它当做概率和前面2分类的损失一样,多分类的损失只需要再把这个结果进行对数似然损失的计算即可,即 :
最后,会计算每个样本的损失,即上式的平均值. 我们把softmax概率传入对数似然损失得到的损失函数称为 **交叉熵损失. **
在pytorch中有两种方法实现交叉熵损失:
pythoncriterion = 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)
带权损失定义为:
其中就是把 作为 ,把真实数值 作为权重
训练的流程:
1.实例化模型,设置模型为训练模式
2.实例化优化器类,实例化损失函数
3.获取,遍dataloader4,梯度为0
5.进行向前计算
6.计算损失
7,反向传播
8.更新参数
pythonmnist_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
pythonfor idx%100 == 0 #
torch.save(mnist_net.state_dict(), "" )
本文作者:wenY
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!