准备数据集的方法前面已经讲过,但是通过前面的内容可知,调用MNIST返回的结果中图像数据是一个image对象,需要对其进行处理。
为了进行数据的处理,我们需要学习torchvision.transform
的方法
torchvision.transform.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],三个通道的颜色互相叠加,形成了各种颜色
实例如下:
from torchvision import transforms
import numpy as np
data = np.random.randint(0,255,size=12)#随机生成12个0-255的数字
img = data.reshape(2,2,3)#将这12个数字的形状改成2 2 3
print(img.shape)
img_tensor = transforms.ToTensor()(img)#转化成tensor
print(img_tensor)
print(img_tensor.shape)
输出如下:
(2, 2, 3)
tensor([[[ 22, 78],
[ 13, 167]],
[[153, 107],
[102, 100]],
[[ 10, 64],
[ 9, 89]]], dtype=torch.int32)
torch.Size([3, 2, 2])
可见,使用transforms.ToTensor()(img)
转化成tensor类型后,这个新对象的形状变成[3,2,2](原来是(2,2,3)),相当于torch.tensor(img).permute(2,0,1)
即:
from torchvision import transforms
import numpy as np
import torch
data = np.random.randint(0,255,size=12)
img = data.reshape(2,2,3)
print(img)
print(img.shape)
img_tensor = transforms.ToTensor()(img)#转化成tensor
print(img_tensor)
print(img_tensor.shape)
img_t = torch.tensor(img)
print(img_t.permute(2,0,1))
print(img_t.permute(2,0,1).shape)
对应输出如下:
[[[154 131 6]
[113 149 7]]
[[ 11 19 163]
[112 111 97]]]
(2, 2, 3)
tensor([[[154, 113],
[ 11, 112]],
[[131, 149],
[ 19, 111]],
[[ 6, 7],
[163, 97]]], dtype=torch.int32)
torch.Size([3, 2, 2])
tensor([[[154, 113],
[ 11, 112]],
[[131, 149],
[ 19, 111]],
[[ 6, 7],
[163, 97]]], dtype=torch.int32)
torch.Size([3, 2, 2])
对应应用于MNIST中:
import torchvision
from torchvision import transforms
dataset = torchvision.datasets.MNIST(root='./data',train=True,download=True,transform=None)
print(dataset[0])
ret = transforms.ToTensor()(dataset[0][0])
print(ret.size())
#print(ret) 输出这个1*28*28的数组,内容太多不展示了
输出如下:
(, 5)
torch.Size([1, 28, 28])
可见通过transforms.ToTensor
方法,把dataset[0]
元组中第一个img对象转换成了[1,28,28]的tensor类型的数组(对应[通道,高,宽])
注意:transforms.ToTensor
对象中有__calll__
方法,所以可以对其示例能传入数据获取结果。
torchvision.transform.Normalize(mean,std)
给定均值:mean,shape(形状)和图片的通道数相同(指的是每个通道的均值)。
方差:std,和图片的通道数相同(指的是每个通道的方差)
将会把Tensor规范化处理,即:Normalize_image = (image - mean) / std
例如:
from torchvision import transforms
import numpy as np
import torchvision
data = np.random.randint(0,255,size=12)
img = data.reshape(2,2,3)
img = transforms.ToTensor()(img) #转化成tensor
print(img)
print('*' * 50)
norm_img = transforms.Normalize((10,10,10),(1,1,1))(img)#进行规范化处理
print(norm_img)
输出如下:
tensor([[[103, 6],
[157, 226]],
[[ 17, 119],
[176, 37]],
[[ 6, 137],
[173, 193]]], dtype=torch.int32)
**************************************************
tensor([[[ 93, -4],
[147, 216]],
[[ 7, 109],
[166, 27]],
[[ -4, 127],
[163, 183]]], dtype=torch.int32)
其中,93=(103-10)/1,10是均值,1是方差
torchvision.transforms.Compose(transforms)
将多个transform组合起来使用
例如:
transforms.Compose([
torchvision.transforms.ToTensor(),#先转化为Tensor
torchvision.transforms.Normalize(mean,std) #再进行正则化
])
from torch.utils.data import DataLoader
from torchvision.transforms import Compose,ToTensor,Normalize
from torchvision.datasets import MNIST
#准备数据集
transform_fn = Compose([ #定义数据处理函数,完成对数据的totorch处理和标准化
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=2,shuffle=True)#设置加载器
for i in enumerate(data_Loader):
print(i)
补充:全连接层:当前一层的神经元和前一层的神经元相互链接,其核心操作就是y=wx,即矩阵的乘法,实现对前一层数据的变换。
模型的构建使用了一个三层的神经网络,其中包含两个全连接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果。
那么在这个模型中有三个地方需要注意:
激活函数如何使用
每一层数据的形状
模型的损失函数
常见的激活函数Relu,它实现对数据中所有的负数置为零,其余0和正数原样输出的效果,它由import torch.nn.functional as F
提供(这个包提供了很多激活函数),F.relu(x)即可对x进行处理。
例如:
b = torch.tensor([-2,-1,0,1,2])
print(F.relu(b))
运行结果:
tensor([0, 0, 0, 1, 2])
构建模型的代码如下:
class MnistModel(nn.Module):
def __init__(self):
super(MnistModel,self).__init__()
# 定义f1方法,使用Linear方法,Linear(输入的形状,输出的形状),将输入的28*28输出为28
self.fc1 = nn.Linear(in_features=28*28,out_features=28)
# 将输入的28输出为10,因为预期输出是10个数字
self.fc2 = nn.Linear(28,10)
def forward(self,input):
'''
:param input: [batch_size,1,28*28] 我们获得的原始数据的样子,即input[0]=batchsize,input[1]=1,input[2]=28*28
:return:
'''
#更改形状,view函数相当于resize的功能,将原来的tensor变换成指的维度,input.size(0)指batchsize的值
x = input.view(input.size(0),28*28) #这里实现把形状转化为[batch_size,28*28]
# x = input.view(-1,28*28) #二者实现效果相同
# x = input.view(input.size(0),-1)#实现效果与未注释的那句等价
#进行全连接操作
x = self.fc1(x)
#使用激活函数处理数据,不会使形状发生变化
x = F.relu(x)
#输出层
out = self.fc2(x)
return out
可见,pytorch在构建模型的时候形状上并不会考虑batch_size。
补充知识
view()函数的功能根reshape类似,用来转换size大小。x = x.view(batchsize, -1)中batchsize指转换后有几行,而-1指在不告诉函数有多少列的情况下,根据原tensor数据和batchsize自动分配列数。x = x.view(x.size(0), -1)相当于2x = x.view(batchsize, -1)。
需要知道,手写数字识别是一个多分类问题,所谓多分类是对比之前的二分类。
sigmoid函数表达式如下:
1 1 + e − Z \frac{1}{1+e^{-Z}} 1+e−Z1
图像如下:
可以看到在趋于正无穷或负无穷时,函数趋近平滑状态,sigmoid函数因为输出范围(0,1),所以二分类的概率常常用这个函数,特点:
值域在0和1之间
函数具有非常好的对称性
函数对输入超过一定范围就会不敏感
现在我们使用多分类应该如何处理呢?
例如下图:
我们把softmax概率传入对数似然损失的损失函数称为交叉熵损失
在pytorch中有两种方法实现交叉熵损失
criterion = nn.CrossEntropyLoss()
loss = criterion(input.taget)
#对输出值计算softmax和取对数
output = = F.log_softmax(x,dim=-1)
#使用torch中带权损失
loss = F.nll_loss(output,target)
带权损失定义为:
l n = − ∑ w i x i l_n=-\sum w_ix_i ln=−∑wixi
其实就是把log(P)
作为x_i
,把真实值Y
作为权重
训练流程:
model = MnistModel()#实例化模型,设置模型为训练模式(默认)
optimizer = Adam(model.parameters(),lr=0.001)#实例化优化器模型
def train(epoch):#epoch 轮的意思
'''实现训练的过程'''
data_loader = get_dataLoader()#获取dataloader
for idx,(input,target) in enumerate(data_loader):
optimizer.zero_grad()#梯度置为零
out_put = model(input)#进行前向计算,调用模型,得到预测值
loss = F.nll_loss(out_put,target)#带权损失
loss.backward()#反向传播(记得梯度置为0),计算梯度
optimizer.step()#梯度更新
if idx%100 == 0:
print(loss.item())
#模型的保存
if idx%100 ==0:
torch.save(model.state_dict(),'./model/model.pkl')
torch.save(optimizer.state_dict(), './model/optimizer.pkl')
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 test():
loss_list = []
acc_list = []
test_dataLoader = get_dataLoader(train=False,batch_size=TEST_BATCH_SIZE)
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] target:[batch_size]
pred = output.max(dim=-1)[-1] #第一个-1表示在最后一个维度(行上)取得最大值,第二个-1表示同时输出对应位置
cur_acc = pred.eq(target).float().mean()
acc_list.append(cur_acc)
print('平均准确率:',np.mean(acc_list),'平均损失',np.mean(loss_list))
'''
该算法的核心思想是通过对比训练值和测试值中的最大值是否相同,来评估该项目的好坏
'''
import numpy as np
import os
from torch.utils.data import DataLoader
from torchvision.transforms import Compose,ToTensor,Normalize
from torchvision.datasets import MNIST
import torch
import torch.nn.functional as F
import torch.nn as nn
from torch.optim import Adam
BATCH_SIZE = 128
TEST_BATCH_SIZE = 1000
#准备数据集
def get_dataLoader(train = True,batch_size = BATCH_SIZE):
transform_fn = Compose([ # 定义数据处理函数,完成对数据的totorch处理和标准化
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
#构建数据模型
class MnistModel(nn.Module):
def __init__(self):
super(MnistModel,self).__init__()
# 定义f1方法,使用Linear方法,Linear(输入的形状,输出的形状),将输入的28*28输出为28
self.fc1 = nn.Linear(in_features=28*28,out_features=28)
# 将输入的28输出为10,因为预期输出是10个数字
self.fc2 = nn.Linear(28,10)
def forward(self,input):
'''
:param input: [batch_size,1,28*28] 我们获得的原始数据的样子,即input[0]=batchsize,input[1]=1,input[2]=28*28
:return:
'''
#更改形状,view函数相当于resize的功能,将原来的tensor变换成指的维度,input.size(0)指batchsize的值
x = input.view(input.size(0),28*28) #这里实现把形状转化为[batch_size,28*28]
# x = input.view(-1,28*28) #二者实现效果相同
# x = input.view(input.size(0),-1)#实现效果与未注释的那句等价
#进行全连接操作
x = self.fc1(x)
#使用激活函数处理数据,不会使形状发生变化
x = F.relu(x)
#输出层
out = self.fc2(x)
return F.log_softmax(out,dim=-1) #在最后一个维度上进行操作,dim是维度的意思
model = MnistModel()#实例化模型,设置模型为训练模式(默认)
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):#epoch 轮的意思
'''实现训练的过程'''
data_loader = get_dataLoader()#获取dataloader
for idx,(input,target) in enumerate(data_loader):
optimizer.zero_grad()#梯度置为零
out_put = model(input)#进行前向计算,调用模型,得到预测值
loss = F.nll_loss(out_put,target)#带权损失
loss.backward()#反向传播(记得梯度置为0),计算梯度
optimizer.step()#梯度更新
# if idx%100 == 0:
# print(loss.item())
#模型的保存
if idx%100 ==0:
torch.save(model.state_dict(),'./model/model.pkl')
torch.save(optimizer.state_dict(), './model/optimizer.pkl')
def test():
loss_list = []
acc_list = []
test_dataLoader = get_dataLoader(train=False,batch_size=TEST_BATCH_SIZE)
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] target:[batch_size]
pred = output.max(dim=-1)[-1] #第一个-1表示在最后一个维度(行上)取得最大值,第二个-1表示同时输出对应位置
cur_acc = pred.eq(target).float().mean()
acc_list.append(cur_acc)
print('平均准确率:',np.mean(acc_list),'平均损失',np.mean(loss_list))
if __name__ == '__main__':
# for i in range(3):#训练三轮
# train(i)
# loader = get_dataLoader(False)
# for input,lable in loader:
# print(lable.size())
# break
test()
for i in range(5):
train(i)
test()
更多Pytorch知识梳理,请参考: pytorch学习笔记
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。