文章目录
- 1.MNIST
- 2.数据预处理
- 2.1相关包
- 2.2数据载入和预处理
- 3.网络结构
- 4.优化器、损失函数、网络训练以及可视化分析
- 4.1定义优化器
- 4.2网络训练
- 4.3可视化分析
- 5.测试
- 6.改进
1.MNIST
MNIST下载地址:http://yann.lecun.com/exdb/mnist/。
下载好了之后,请建好如下目录(其中data的名字你可以随便改,其他不要变):
data_path = 'D:\lbq\lang\pythoncode\data'
也就是说:在官网上下载好的4个文件放在raw目录下。
2.数据预处理
2.1相关包
import torch
from torch.utils import data # 获取迭代数据
from torch.autograd import Variable # 获取变量
import torchvision
from torchvision.datasets import mnist # 获取数据集和数据预处理
import matplotlib.pyplot as plt
2.2数据载入和预处理
# 数据集的预处理
data_tf = torchvision.transforms.Compose(
[
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize([0.5],[0.5])
]
)
data_path = r'D:\lbq\lang\pythoncode\data'
# 获取数据集
train_data = mnist.MNIST(data_path,train=True,transform=None,download=True)
test_data = mnist.MNIST(data_path,train=False,transform=None,download=True)
解释:
- 其中data_path后面的r表示防止后面的字符串转义。
- transform=None是为了先演示图片,否则转换之后就变成了向量,不是图片了。
此时我们发现,其在raw目录自动解压了4个gz文件,并在processed目录下面生成了两个文件。
/raw/
/processed/
其他一些参数解释如下:
该网址为:https://pypi.org/project/torchvision/0.1.8/#mnist。
一些操作:
1.展示训练集的第0和第1张图片:
可以看到,train_data[0]是一个元组,第一项是图片[0],第二项5是标签[1]。
2.查看总共有多少张图片:
不过图片并不能被计算机识别,要转化为向量,所以用到transform
# 数据集的预处理
data_tf = torchvision.transforms.Compose(
[
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize([0.5],[0.5])
]
)
data_path = r'D:\lbq\lang\pythoncode\data'
# 获取数据集
train_data = mnist.MNIST(data_path,train=True,transform=data_tf,download=True)
test_data = mnist.MNIST(data_path,train=False,transform=data_tf,download=True)
这个时候已经不能查看图片了,不过其仍然是元组,第一项是一个向量,第二项是一个标签。
我们发现,一张图片用(1,28,28)表示,1表示图片只有一个channel(通道),因为这个是黑白照片。彩色有3个。后面的28*28代表像素,可以想象为一个平面。由于我们前面标准化了,所以每个数值都比较小,不然可能是(0,255)之间的数。
batch_size=32
train_loader = data.DataLoader(train_data,batch_size=batch_size,shuffle=True,pin_memory=True)
test_loader = data.DataLoader(test_data,batch_size=batch_size)
帮助:
- DataLoader可以处理DataSet类型的东西,而train_data就是这个类型,之前图片已经展示过了。
- DataLoader有什么用?其叫做数据加载器,结合了数据集和取样器(之前train_data只是一个数据集),并且可以提供多个线程处理数据集。在训练模型时使用到此函数,用来把训练数据分成多个小组,此函数每次抛出一组(batch_size)数据。直至把所有的数据都抛出。
- shuffle是洗牌的意思,每个epoch就洗一次,打乱训练数据的顺序。这个很多人都不明白,我看了都是说的是错的。注意,一个epoch包括很多组(batch_size)。比如1,3,4为训练数据,batch_size是1,那么第一次shuffle一下,随机变成341,那么根据batch_size,喂给模型训练的顺序是3,4,1。喂完之后,到第2个epoch,又重新shuffle,随便变成413,那么根据batch_size,喂给模型训练的顺序是4,1,3。以此类推。
- pin_memory据说可以使得训练速度更快,随便你吧。
3.网络结构
图1 卷积层 | 图2 全连接层 |
代码实现:
#导入相关的包
import torch.nn as nn#网络结构
import torch.nn.functional as F
import torch.optim as optim#优化器
class CNN(nn.Module):
def __init__(self):
super(CNN,self).__init__()
self.conv1=nn.Conv2d(1,20,5,1)
self.conv2=nn.Conv2d(20,50,5,1)
self.fc1=nn.Linear(4*4*50,500)
self.fc2=nn.Linear(500,10)
def forward(self,x):
#x是一个batch_size的数据
#x:1*28*28
x=F.relu(self.conv1(x))
#20*24*24
x=F.max_pool2d(x,2,2)
#20*12*12
x=F.relu(self.conv2(x))
#50*8*8
x=F.max_pool2d(x,2,2)
#50*4*4
x=x.view(-1,50*4*4)
#压扁成了行向量,(1,50*4*4)
x=F.relu(self.fc1(x))
#(1,500)
x=self.fc2(x)
#(1,10)
return F.log_softmax(x,dim=1)
帮助:
- Conv2d是什么?max_pool2d又是什么?见:简要解释什么是Conv1d,Conv2d,Conv3d。
-
Conv2d(20,50,5,1)
指的就是输入输入图片的通道是20个,我们卷积核的大小是(20,5,5),相当于是一个立方体,对着输入(也是立方体)进行卷积,然后移动,移动步长是1。这样的卷积核话只能生成一个通道的特征图对吧?没事,我们有50个卷积核,可以并行卷积,那么就得到了50个通道的特征图,也就是说,输出是50通道的。 - 为什么用log_softmax()?见:(详细全面)softmax和log_softmax的联系和区别,NLLLOSS和CrossEntropyLoss的联系和区别。
- view是用来转换维度的,和numpy中的reshape一样。
4.优化器、损失函数、网络训练以及可视化分析
优化器:SGD(随机梯度下降)
损失函数:NLLLOSS(交叉熵损失函数的最新版,见。)
4.1定义优化器
lr=0.01#学习率
momentum=0.5
device=torch.device("cuda" if torch.cuda.is_available() else "cpu" )
model=CNN().to(device)
optimizer=optim.SGD(model.parameters(),lr=lr,momentum=momentum)
4.2网络训练
def train(model,device,train_loader,optimizer,epoch,losses):
model.train()
for idx,(t_data,t_target) in enumerate(train_loader):
t_data,t_target=t_data.to(device),t_target.to(device)
pred=model(t_data)#batch_size*10
loss=F.nll_loss(pred,t_target)
#SGD
optimizer.zero_grad()#将上一步的梯度清0
loss.backward()#重新计算梯度
optimizer.step()#更新参数
if idx%100==0:
print("epoch:{},iteration:{},loss:{}".format(epoch,idx,loss.item()))
losses.append(loss.item()) #每100批数据采样一次loss,记录下来,用来画图可视化分析。
训练
num_epochs=2
losses=[]#记录起来用来画图的,可以画出损失随着迭代次数而下降。
from time import *
begin_time=time()#测试我们的模型训练要花多久。
for epoch in range(num_epochs):
train(model,device,train_loader,optimizer,epoch,losses)
end_time=time()
训练结果:
模型训练所花时间为22.9秒:
4.3可视化分析
import matplotlib.pyplot as plt
len_l=len(losses)
x=[i for i in range(len_l)]
figure=plt.figure(figsize=(20,8),dpi=80)
plt.plot(x,losses)
plt.show()
可以发现,loss在前期下降的很快,后面基本就稳定了。
上面的代码基本上是套路,除了输出语句和参数之外,能改的东西很少。
5.测试
def test(model,device,test_loader):
model.eval()
correct=0#预测对了几个。
with torch.no_grad():
for idx,(t_data,t_target) in enumerate(test_loader):
t_data,t_target=t_data.to(device),t_target.to(device)
pred=model(t_data)#batch_size*10
pred_class=pred.argmax(dim=1)#batch_size*10->batch_size*1
correct+=pred_class.eq(t_target.view_as(pred_class)).sum().item()
acc=correct/len(test_data)
print("accuracy:{},".format(acc))
帮助:
- tensor才可以eq,返回的是true,false.numpy也可以,使用a==b
- view_as主要是怕一个是行向量,一个是列向量,不同形状不可以eq。
- .item()是用来将只有一个元素的tensor转化为一个数值的,即把维度去掉。例如tensor([[[0]]]),使用.item()就变成0。
- 这里len(test_loader)是指的是有多少批数据,len(test_data)是有多少张图片。也就是说:len(test_loader)=len(test_data)/batch_size上取整。
测试:
test(model,device,test_loader)
测试结果:
6.改进
最后给出改进方向,也就是说如果你还要提升准确率或者调参什么的,我应该怎么做?
- 网络结构,比如增加网络层数。
- 优化器,上面是SGD,但是还有很多其他的,比如Adam,见https://pytorch.org/docs/stable/generated/torch.optim.Adam.html?highlight=adam#torch.optim.Adam。而且参数几乎一样,直接换就行。
- 学习率lr,比如调低或者调高。
- 批处理batch_size大小
- 更换损失函数,别换交叉熵,因为上面就是交叉熵的最新版,还有其他的,比如平方损失。nll_loss对应位置换成mse_loss,参数也几乎一样,保持不变。见https://pytorch.org/docs/stable/generated/torch.nn.functional.mse_loss.html?highlight=mse#torch.nn.functional.mse_loss。
- 增加训练趟数num_epochs或者减少。
- 池化操作pooling,使用平均值AvgPool2d,参数也几乎和MaxPool2d一摸一样,见https://pytorch.org/docs/stable/generated/torch.nn.AvgPool2d.html?highlight=pool#torch.nn.AvgPool2d。
- 模型集成,比如多训练几个模型,然后使用投票的方法决定测试图片属于哪个类别。