目录
2.torchvision.transforms.Normalize:
3.torchvision.transforms.Compose:
一.数据处理知识
使用torch中自带的数据MNIST,里面的数据是图形数据,返回一个image对象
MNIST数据是图片中有1-9的数字,而我们要做的就是训练一个模型来识别这些数字
1.torchvision.transforms方法
这个方法是处理图形数据的,里面主要有:
torchvision.transforms.ToTensor:把一个取值范围为[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray转化为[C,H,W]。其中H代表高,W代表宽,C代表通道数(一个通道代表一种颜色,黑白的就一个通道就可用代表了,而彩色的要三个通道RGB来表示)。
from torchvision import transforms
import numpy as np
# 生成12个在0-255之间的数据
data=np.random.randint(0,255,size=12)
# 将数据形状改变成三维的
img=data.reshape(2,2,3)
print(img)
# 通过ToTensor改变后
img_tensor=transforms.ToTensor()(img)
print(img_tensor)
# 运行后可用看到本来的(2,2,3)变成了(3,2,2)
2.torchvision.transforms.Normalize:
规范化处理,给定均值mean,shape,图片的通道数相同(指每个通道的均值相同),方差:std,和图片的通道数相同(指的是每个通道的方差),那么就对Tensor规范化处理。也就是将这个图片中的每个像素点的数据减去均值比上方差,与机器学习中的规则化一样。
from torchvision import transforms
import numpy as np
import torchvision
# 生成12个在0-255之间的数据
data=np.random.randint(0,255,size=12)
# 将数据形状改变成三维的
img=data.reshape(2,2,3)
print(img)
# 通过transforms改变后
img_tensor=transforms.ToTensor()(img)
print(img_tensor)
# 运行后可用看到本来的(2,2,3)变成了(3,2,2)
# Normalize中第一个参数传的是每一列的平均值,第二个是方差
norm_img=transforms.Normalize((10,10,10),(1,1,1))(img_tensor.float())
print(norm_img)
上面代码中平均值和方差需要自己计算,去对应的平均值和方差就可用了,比如平均值是所有的平均值,那么方差也要所有数的方差,如果是一列的那么都是一列的。
3.torchvision.transforms.Compose:
这个的作用就是将多个transforms方法组合起来,比如将上面的1和2方法组合
二.准备数据
准备数据集,其中0.1307,0.3081是MNIST数据的均值和方差
import torch
import torchvision
# 导入数据,train=True表示这是训练集,如果False表示测试集。download表示是否下载
dataset=torchvision.datasets.MNIST('/data',train=True,download=True)
# 将规范化和数据转换合并为一个方法
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.1307,),(0.3081,))
])
# 准备数据迭代器
train_dataloader=torch.utils.data.DataLoader(dataset,batch_size=64,shuffle=True)
三.构建模型
1.模型形状
这里构建一个简单的三层神经网络,其中包括两个全连接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交到下一个全连接层。
2.激活函数
常见的激活函数有:Relu,sigmoid等,这里选用Relu,因为这里是多分类问题,选这个简单。
Relu激活函数由torch.nn.functional提供,函数原理是将小于0变成0,大于0的不变
3.模型中的数据形状
1.原始的输出形状为[batch_size,1,28,28]----------[样本个数,通道数,宽,高]
2.进行形状修改:[batch_size,28*28],(全连接层要进行矩阵乘法,神经网络介绍哪里讲过。比如由28个数,因为是全链接所以一个要乘全部,所以就成了28*28个了)
3.第一个全连接层的输出形状是[batch_size,28]这里的28是自己设定的。
4.使用激活函数不会修改数据的形状
5.第二个全连接层的输出形状[batch_size,10]因为这个数据里由十个类别,对应十个数字0-9
import torch
from torch import nn
import torch.nn.functional as F
import torchvision
# 导入数据,train=True表示这是训练集,如果False表示测试集。download表示是否下载
dataset=torchvision.datasets.MNIST('/data',train=True,download=True)
# 将规范化和数据转换合并为一个方法
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.1307,),(0.3081,))
])
# 准备数据迭代器
train_dataloader=torch.utils.data.DataLoader(dataset,batch_size=64,shuffle=True)
class MnistNet(nn.Module):
def __init__(self):
super(MnistNet,self).__init__()
self.fc1=nn.Linear(28*28*1,28) # 定义第一层的w和b输入和输出形状
self.fc1=nn.Linear(28,10) # 定义第二层的w和b的输入和输出形状
def forward(self,x):
x=x.view(-1,28*28*1) # 该表数据形状,-1表示该位置根据后面的形状自动调整
x=self.fc1(x) # 将数据传入后第一层对这个数据进行计算后输出为形状[batch_size,28]
x=F.relu(x) # 激活函数
x=self.fc2(x) # 将激活函数处理后的数据放到第二层中处理后就得到[batch_size,10]的结果了
可以发现,pytorch在构建模型的时候形状上并不会考虑batch_size(样本数量)
四.模型的损失函数
因为是多分类问题,所以这里使用sotfmax
五.模型的训练
1.实例话模型,蛇者模型为训练模式
2.实例话优化器类,实例话损失函数
3.获取,遍历dataloader
4.梯度置0
5.进行向前计算
6.计算损失
7.反向传播
8.更新参数
# 模型对象
mnist_net = MnistNet()
# 实例化优化器类
optimizer = optim.Adam(mnist_net.parameters(), lr=0.001)
# 创建一个训练函数
def train(epoch):
# 设置一个bool值来判断是否是训练
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 = mnist_net(data) # 进行向前计算
loss = F.nll_loss(output, target) # 计算出带权损失
loss.backward() # 进行反向传播,计算梯度
optimizer.step() # 参数更新
# 打印看一下损失变化
if idx % 10 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, idx * len(data), len(train_dataloader.dataset),
100. * idx / len(train_dataloader), loss.item()))
六.模型的保存和加载
# torch自带的save保存函数,第一个参数为需要保存的数据,第二个参数为保存的路径
torch.save(mnist_net.state_dict(),"保存的路径/文件名") #保存模型参数
torch.save(optimizer.state_dict(), '保存的路径/文件名') #保存优化器参数
七.模型的加载
# 因为模型继承了nn.Module,里面有一个方法load_state_dict,传入文件路线即可,注意路径要用torch.load修饰一下
mnist_net.load_state_dict(torch.load("模型参数的文件路径"))
optimizer.load_state_dict(torch.load("优化器的文件路径"))
八.模型评估
1.不需要计算梯度,因为我们只需要计算出预测值和误差就可用对模型评估了
2.准确率的计算:因为我们使用的损失函数是sotfmax,那么最大值就是预测值
3.torch中提供了一个获取最大值的方法torch.max,里面传入数据和一个参数keepdim,=0表示每列最大值,1表示每行最大值,输出返回最大值以及对应的索引值。因此,predict = torch.max(outputs.data, 1)[1] 是计算模型中每个类别的最大值并返回其索引值,即该类别的标签值。
4.找到最大值后与真实值对比
def test():
# 创建一个变量来记录平均损失
test_loss = 0
# 记录预测样本数
correct = 0
mnist_net.eval() #设置模型为评估模式
test_dataloader = get_dataloader(train=False) #获取测试数据
with torch.no_grad(): #不计算其梯度
for data, target in test_dataloader: # 遍历数据和真实值
output = mnist_net(data) # 得到我们训练后模型的预测值
test_loss += F.nll_loss(output, target, reduction='sum').item() # 计算本次的误差
pred = output.data.max(1, keepdim=True)[1] #获取最大值的位置,[batch_size,1]
correct += pred.eq(target.data.view_as(pred)).sum() #预测准备样本数累加
test_loss /= len(test_dataloader.dataset) #计算平均损失
print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
test_loss, correct, len(test_dataloader.dataset),
100. * correct / len(test_dataloader.dataset)))
九.完整代码:
注意要加入文件路径
import torch
from torch import nn, optim
import torch.nn.functional as F
import torchvision
# 创建一个训练集中一组数据中的样本数量,比如一组64张图片
train_batch_size = 64
# 创建一个测试集的样本数量
test_batch_size = 1000
img_size = 28
# 构建一个加载参数和优化器类的方法
def get_dataloader(train=True):
assert isinstance(train,bool),"train 必须是bool类型"
# 获取训练数据
dataset = torchvision.datasets.MNIST('/data', train=train, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.1307,), (0.3081,)),]))
#准备数据迭代器
batch_size = train_batch_size if train else test_batch_size
dataloader = torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=True)
return dataloader
# 模型类
class MnistNet(nn.Module):
def __init__(self):
super(MnistNet,self).__init__()
self.fc1=nn.Linear(28*28*1,28) # 定义第一层的w和b输入和输出形状
self.fc1=nn.Linear(28,10) # 定义第二层的w和b的输入和输出形状
def forward(self,x):
x=x.view(-1,28*28*1) # 该表数据形状,-1表示该位置根据后面的形状自动调整
x=self.fc1(x) # 将数据传入后第一层对这个数据进行计算后输出为形状[batch_size,28]
print(x)
x=self.fc2(x) # 将激活函数处理后的数据放到第二层中处理后就得到[batch_size,10]的结果了
return F.log_softmax(x,dim=-1)
# 模型对象
mnist_net=MnistNet()
# 实例化优化器类
optimizer=optim.Adam(mnist_net.parameters(),lr=0.001)
# 训练集的损失列表
train_loss_list=[]
def train(epoch):
# 设置一个bool值来决定是训练还是测试
mode = True
mnist_net.train(mode=mode)
train_dataloader = get_dataloader(train=mode) #得到训练数据迭代器
print(len(train_dataloader.dataset))
print(len(train_dataloader))
for idx,(data,target) in enumerate(train_dataloader):
optimizer.zero_grad() # 将梯度置为0
output = mnist_net(data) # 接收这次的训练后的数据
loss = F.nll_loss(output,target) #对数似然损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 跟新参数
if idx % 10 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, idx * len(data), len(train_dataloader.dataset),
100. * idx / len(train_dataloader), loss.item()))
# 保存这次的损失
train_loss_list.append(loss.item())
# 保存本次的参数数据w和b等等
torch.save(mnist_net.state_dict(),"文件路径/文件名")
# 保存优化器类
torch.save(optimizer.state_dict(), '文件路径/文件名')
# 模型评估方法
def test():
# 创建一个变量来记录平均损失
test_loss = 0
# 记录预测样本数
correct = 0
mnist_net.eval() #设置模型为评估模式
test_dataloader = get_dataloader(train=False) #获取测试数据
with torch.no_grad(): #不计算其梯度
for data, target in test_dataloader: # 遍历数据和真实值
output = mnist_net(data) # 得到我们训练后模型的预测值
test_loss += F.nll_loss(output, target, reduction='sum').item() # 计算本次的误差
pred = output.data.max(1, keepdim=True)[1] #获取最大值的位置,[batch_size,1]
correct += pred.eq(target.data.view_as(pred)).sum() #预测准备样本数累加
test_loss /= len(test_dataloader.dataset) #计算平均损失
print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
test_loss, correct, len(test_dataloader.dataset),
100. * correct / len(test_dataloader.dataset)))
if __name__ == '__main__':
test()
for i in range(5): # 这个循环代表模拟几轮,因为每次的参数都是保存读取的,所以模拟的越多越精确
train(i)
test()