基于pytorch框架的手写数字识别(mnist数据集)
手写数字识别
前段时间开始学习pytorch,学习了一点pytorch的小语法,在网上找到了pytorch入门写CNN的代码,自己尝试读懂加上注释。更多的了解一下pytorch,代码注释写的还算清楚,在阅读代码之前可以看一下我收获的知识都是在代码里遇到的不会的语句,我自己通过阅读别博客获取的知识,大多数都是torch在读取数据的操作。先读一下这个有利于阅读代码。
收获的知识:
1.torch.maual_seed()
在神经网络中,好比BP神经网络里面所有的参数都是随机的,我们使用这样方式来生成随机数,这些随机数是固定的,可以复现的
import torch
torch.manual_seed(1)
print(torch.rand(2))
连续执行两次:
1.
2.
import torch
#torch.manual_seed(1)
print(torch.rand(2))
2.
2.train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
刚开始不知道什么意思 后来看到是 torch.utils.data的简称
在在拜读了这篇博客了以后
我了解到了 torch.utlis.data.DataLoader是pytorch读取数据的重要接口,pytorch模型的训练数据大多会用到这个接口来读取数据,该接口的源代码(也来自于我看的那篇博文):
torch.utils.data.DataLoader(
dataset,#数据加载
batch_size = 1,#批处理大小设置
shuffle = False,#是否进项洗牌操作
sampler = None,#指定数据加载中使用的索引/键的序列
batch_sampler = None,#和sampler类似
num_workers = 0,#是否进行多进程加载数据设置
collate_fn = None,#是否合并样本列表以形成一小批Tensor
pin_memory = False,#如果True,数据加载器会在返回之前将Tensors复制到CUDA固定内存
drop_last = False,#True如果数据集大小不能被批处理大小整除,则设置为删除最后一个不完整的批处理。
timeout = 0,#如果为正,则为从工作人员收集批处理的超时值
worker_init_fn = None )
该接口的目的是将你需要的数据集按照 bitch_size(批处理大小设置),shuffle(是否进行洗牌)把数据封装成一个Batch Size大小的Tensor,用于后面的训练。
3.torchvision.datasets.MNIST(root='./mnist/', train=False)
接着我又去百度,拜读了一篇博客和上面的解读torch.utils.data.DataLoader的同一个作者,博客地址
阅读了他的博客我得知了,PyTorch框架中有一个非常重要且好用的包:torchvision,该包主要由3个子包组成,分别是:torchvision.datasets、torchvision.models、torchvision.transforms。torchvision.datasets。torchvision.datasets这个包中包含MNIST、FakeData、COCO、LSUN、ImageFolder、DatasetFolder、ImageNet、CIFAR等一些常用的数据集,并且提供了数据集设置的一些重要参数设置,可以通过简单数据集设置来进行数据集的调用。
torchvision.datasets.MNIST(root,train = True,transform = None,target_transform = None,download = False )
# 参数介绍:
#root(string) - 数据集的根目录在哪里MNIST/processed/training.pt 和 MNIST/processed/test.pt存在。
# train(bool,optional) - 如果为True,则创建数据集training.pt,否则创建数据集test.pt。
#download(bool,optional) - 如果为true,则从Internet下载数据集并将其放在根目录中。如果已下载数据集,则不会再次下载。
# transform(callable ,optional) - 一个函数/转换,它接收PIL图像并返回转换后的版本。例如,transforms.RandomCrop
# target_transform(callable ,optional) - 接收目标并对其进行转换的函数/转换。
那么我遇到这个代码的意思就是,把根目录下的mnist作为训练集
4.torch.unsqueeze
根据英文单词我也判断出了这个是不压缩就是 提高维度的操作
有解读增加维度和压缩的操作
大概是的意思就是 如果我指定的位置的维度如果为1,我就删除这个维度,或者在我指定的维度增加一个维度
5.nn.Conv2d(pytorch中的二维卷积)
一般 nn.Conv1d 一维度的卷积用于文本处理,二维 多用于图像的处理
nn.Conv2d(16, 32, 5, 1, 2),
W2=(28+2X2-5)/1+1=28
H2=(28+2X2-5)/1+1=28
输出的图片还是 28X28,
代码里有些关于卷积的注释
刚刚开始的我一直理解不了,在这段代码里面有些地方会返回2个参数、
其实是因为这段代码里面包含了数据可视化,CNN神经网络输出也会输出两个数据一个是真实值,一个用于做数据可视化的(现阶段我想深入了解pytorch就先不去管数据可视化)
代码部分():
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.utils.data as Data
import torchvision
import matplotlib.pyplot as plt
torch.manual_seed(1) # reproducible 可以复现的
# Hyper Parameters
EPOCH = 1 # 为节约时间我们只训练一轮
BATCH_SIZE = 50
LR = 0.001 # 谁知学习率
DOWNLOAD_MNIST = True # 如果数据集你已经下载好了就设置为False
# Mnist digits dataset
train_data = torchvision.datasets.MNIST(
root='./mnist/',
train=True, # this is training data
transform=torchvision.transforms.ToTensor(), # Converts a PIL.Image or numpy.ndarray to
# torch.FloatTensor of shape (C x H x W) and normalize in the range [0.0, 1.0]
download=DOWNLOAD_MNIST, # download it if you don't have it
)
# plot one example
print(train_data.train_data.size()) # (60000, 28, 28)
print(train_data.train_labels.size()) # (60000)
plt.imshow(train_data.train_data[0].numpy(), cmap='gray')#打印出数据集中第一个手写数字的灰度图片
plt.title('%i' % train_data.train_labels[0])
plt.show()
# Data Loader for easy mini-batch return in training, the image batch shape will be (50, 1, 28, 28)
#数据加载器,便于训练中的小批量返回,图像批量形状为(50, 1, 28, 28)28X28是这个灰度图片的像素,封装成(50,1,28,28)这样一个tensor
# shuffle=True 对数据进行洗牌
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# convert test data into Variable, pick 2000 samples to speed up testing
#将测试数据转换为变量,选取2000个样本加速测试
test_data = torchvision.datasets.MNIST(root='./mnist/', train=False)#--->测试数据
#traindata将原来的[0-255]压缩到了[0.0, 1.0]这个区间,所以test data也要/255做同样处理
test_x = Variable(torch.unsqueeze(test_data.test_data, dim=1), volatile=True).type(torch.FloatTensor)[:2000]/255.
# shape from (2000, 28, 28) to (2000, 1, 28, 28), value in range(0,1)
#为了节省时间只取了2000个测试
test_y = test_data.test_labels[:2000]
class CNN(nn.Module):#继承了nn.Model
def __init__(self):
#继承了 CNN 父类的的属性
super(CNN, self).__init__()#用父类的初始化方式来初始化所继承的来自父类的属性
#按照网络的前后顺序定义1号网络
self.conv1 = nn.Sequential( # input shape (1, 28, 28)
nn.Conv2d(#这里的nn.Conv2d使用一个2维度卷积
in_channels=1, #in_channels:在文本应用中,即为词向量的维度
out_channels=16, # out_channels:卷积产生的通道数,有多少个out_channels,就需要多少个一维卷积(也就是卷积核的数量)
kernel_size=5, #kernel_size:卷积核的尺寸;卷积核的第二个维度由in_channels决定,所以实际上卷积核的大小为kernel_size * in_channels
stride=1, #步长,每次移动的单位格子
padding=2, #padding:对输入的每一条边,补充0的层数
), # output shape (16, 28, 28)
nn.ReLU(), #激活函数ReLU # activation
#在2X2的池化层里选出最大值
nn.MaxPool2d(kernel_size=2), # choose max value in 2x2 area, output shape (16, 14, 14)
)
#按照网络的前后顺序定义2号网络,
self.conv2 = nn.Sequential( # input shape (1, 28, 28)
#使用一个二维卷积
nn.Conv2d(16, 32, 5, 1, 2), # output shape (32, 14, 14)
nn.ReLU(), # activation
nn.MaxPool2d(2), # output shape (32, 7, 7)
)
#全连接层
self.out = nn.Linear(32 * 7 * 7, 10) #因为在pytorch中做全连接的输入输出都是二维张量,不同于卷积层要求输入4维张量
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = x.view(x.size(0), -1) # flatten the output of conv2 to (batch_size, 32 * 7 * 7)
output = self.out(x)
#output是我们的真实值,而x是用于做数据可视化的参数
return output, x # return x for visualization
#把CNN神经网络类实例化
cnn = CNN()
print(cnn) # net architecture
#设置一个优化器
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR) # optimize all cnn parameters
#设置损失函数
loss_func = nn.CrossEntropyLoss() # the target label is not one-hotted
#这些是做数据可视化的,先不做深入了解
# following function (plot_with_labels) is for visualization, can be ignored if not interested
from matplotlib import cm
try: from sklearn.manifold import TSNE; HAS_SK = True
except: HAS_SK = False; print('Please install sklearn for layer visualization')
def plot_with_labels(lowDWeights, labels):
plt.cla()
X, Y = lowDWeights[:, 0], lowDWeights[:, 1]
for x, y, s in zip(X, Y, labels):
c = cm.rainbow(int(255 * s / 9)); plt.text(x, y, s, backgroundcolor=c, fontsize=9)
plt.xlim(X.min(), X.max()); plt.ylim(Y.min(), Y.max()); plt.title('Visualize last layer'); plt.show(); plt.pause(0.01)
plt.ion()
# training and testing
for epoch in range(EPOCH):#节约时间只循环一次也就是训练一轮
#当我们迭代我们的数据集的时候批量处理数据
#step 是循环的次数, (x,y) 对应每一个train_loader数据中的target和真实值
for step, (x, y) in enumerate(train_loader): # gives batch data, normalize x when iterate train_loader
#Variable 类型可以对该类型的数据自动求导
b_x = Variable(x) # batch x
b_y = Variable(y) # batch y
#选取从网络里返回第一个数据作为我们的 真实值
output = cnn(b_x)[0] # cnn output
#在损失函数中 (target(这里也就是这个手写数字是几)-真实值)得到误差
loss = loss_func(output, b_y) # cross entropy loss
#清空这一轮数据的 梯度,不然数据会影响下轮迭代训练 #在每次循环中清零 grad避免累加
optimizer.zero_grad() # clear gradients for this training step
#反向传播计算梯度
loss.backward() # backpropagation, compute gradients
#optimizer的执行step更新指令,更新model的每一个parameter
optimizer.step() # apply gradients
#每训练五十个数据,我们就用当前训练出的模型来预测
if step % 50 == 0:
#用测试集数据来测试,得到两个返回值一个是用于做数据可视化的一个是真实值
test_output, last_layer = cnn(test_x)
#在返回真实值选择最大的一个作为预测值
pred_y = torch.max(test_output, 1)[1].data.squeeze()
#这里 test_y就是灰度图片本身的标签 准确度就是所有的 预测值==标签(测试集预测成功的次数)/测试集所有的数据数量,也就是预测成功率
accuracy = sum(pred_y == test_y) / float(test_y.size(0))
print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.item(), '| test accuracy: %.2f' % accuracy)
#下面是做数据可视化,现阶段主要了解pytorch框架
if HAS_SK:
# Visualization of trained flatten layer (T-SNE)
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
plot_only = 500
low_dim_embs = tsne.fit_transform(last_layer.data.numpy()[:plot_only, :])
labels = test_y.numpy()[:plot_only]
plot_with_labels(low_dim_embs, labels)
plt.ioff()
# print 10 predictions from test data
#输出前十组预测值和真实值, 对比一下预测效果
test_output, _ = cnn(test_x[:10])
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
print(pred_y, 'prediction number')
print(test_y[:10].numpy(), 'real number')
运行截图(我在jupter上跑的):
这个是拿训练好的模型来预测前十个 手写数字,第一行是预测值,第二行是标签,前十个都识别出来了!还是比较成功的。