深度学习- 用Torch实现MNIST手写数字识别

本节代码地址:

https://github.com/vic-w/torch-practice/tree/master/mnist

MNIST是手写数字识别的数据库。在深度学习流行的今天,MNIST数据库已经被大家玩坏了。但是用它来学习卷积神经网络是再好不过的了。这一次,我们就用Torch来实现MNIST数据库的识别。

这一次的代码用到了mnist库,如果之前没有安装,可以在命令行键入:

[plain] view plain copy
  1. luarocks install mnist  

和往常一样,我们要先包含必要的库

[plain] view plain copy
  1. require 'torch'  
  2. require 'nn'  
  3. require 'optim'  
  4. mnist = require 'mnist'  
其中require ’mnist'一句返回了一个mnist的对象。可以用下面两句来获得mnist的图像数据。
[plain] view plain copy
  1. fullset = mnist.traindataset()  
  2. testset = mnist.testdataset() 

将这个模型用Torch的代码实现也非常简单。首先仍然是建立一个容器用来存放各种模块。

[plain] view plain copy
  1. model = nn.Sequential()  

放入一个reshape模块。因为mnist库的原始图片是储存为1列728个像素的。我们需要把它们变成1通道28*28的一个方形图片。
[plain] view plain copy
  1. model:add(nn.Reshape(1, 28, 28))  

接下来要把图片的每个像素除以256再乘以3.2,也就是把像素的取值归一化到0至3.2之间。这相当于Caffe中的Scale模块。
[plain] view plain copy
  1. model:add(nn.MulConstant(1/256.0*3.2))  

然后是第一个卷积层,它的参数按顺序分别代表:输入图像是1通道,卷积核数量20,卷积核大小5*5,卷积步长1*1,图像留边0*0
[plain] view plain copy
  1. model:add(nn.SpatialConvolutionMM(1, 20, 5, 5, 1, 1, 0, 0))  

一个池化层,它的参数按顺序分别代表:池化大小2*2,步长2*2,图像留边0*0
[plain] view plain copy
  1. model:add(nn.SpatialMaxPooling(2, 2 , 2, 2, 0, 0))  

再接一个卷积层和一个池化层,由于上一个卷积层的核的数量是20,所以这时输入图像的通道个数为20
[plain] view plain copy
  1. model:add(nn.SpatialConvolutionMM(20, 50, 5, 5, 1, 1, 0, 0))  
  2. model:add(nn.SpatialMaxPooling(2, 2 , 2, 2, 0, 0))  

在接入全连接层之前,我们需要把数据重新排成一列,所以有需要一个reshape模块
[plain] view plain copy
  1. model:add(nn.Reshape(4*4*50))  

这个参数为什么是4*4*50即800呢?其实是这样算出来的:我们的输入是1通道28*28的图像,经过第一个卷积层之后变成了20通道24*24的图像。又经过池化层,图像尺寸缩小一半,变为20通道12*12。通过第二个卷积层,变为50通道8*8的图像,又经过池化层缩小一半,变为50通道4*4的图像。所以这其中的像素一共有4*4*50=800个。

接下来是第一个全连接层。输入为4*4*50=800,输出为500

[plain] view plain copy
  1. model:add(nn.Linear(4*4*50, 500))  

两个全连接层之间有一个ReLU激活层
[plain] view plain copy
  1. model:add(nn.ReLU())  

然后是第二个全连接层,输入是500,输出是10,也就代表了10个数字的输出结果,哪个节点的响应高,结果就定为对应的数字。
[plain] view plain copy
  1. model:add(nn.Linear(500, 10))  

最后是一个LogSoftMax层,用来把上一层的响应归一化到0至1之间。
[plain] view plain copy
  1. model:add(nn.LogSoftMax())  

模型的建立就完成了。我们还需要一个判定标准。由于我们这一次是要解决分类问题,一般使用nn. ClassNLLCriterion这种类型的标准(Negative Log Likelihood)

[plain] view plain copy
  1. criterion = nn.ClassNLLCriterion()  

为了要达到更好的优化效果,这里需要对model内部参数的初始化做一下特殊的处理。还记得torch会帮我们随机初始化参数吗?我们现在不使用torch的初始化参数,而使用一种更高级的初始化方法,称之为xavier方法。概括来讲,就是根据每层的输入个数和输出个数来决定参数随机初始化的分布范围。在代码里只需要一句:

[plain] view plain copy
  1. model = require('weight-init')(model, 'xavier')  

其中的‘weight-init’指向了与主文件同一文件夹里的weight-init.lua这个文件。xavier方法就在这个文件里面。它是由https://github.com/e-lab/torch-toolbox所实现的。



到这里,网络模型的部分就都已经完成了。我们现在就需要建立评估函数,然后循环迭代就可以了。这些都是例行公事,可以参照前面的代码来写,这里就不在赘述了。


完整的代码在这里,大家可以运行试一试。

[plain] view plain copy
  1. require 'torch'  
  2. require 'nn'  
  3. require 'optim'  
  4. --require 'cunn'  
  5. --require 'cutorch'  
  6. mnist = require 'mnist'  
  7.   
  8. fullset = mnist.traindataset()  
  9. testset = mnist.testdataset()  
  10.   
  11. trainset = {  
  12.     size = 50000,  
  13.     data = fullset.data[{{1,50000}}]:double(),  
  14.     label = fullset.label[{{1,50000}}]  
  15. }  
  16.   
  17. validationset = {  
  18.     size = 10000,  
  19.     data = fullset.data[{{50001,60000}}]:double(),  
  20.     label = fullset.label[{{50001,60000}}]  
  21. }  
  22.   
  23. trainset.data = trainset.data - trainset.data:mean()  
  24. validationset.data = validationset.data - validationset.data:mean()  
  25.   
  26.   
  27. model = nn.Sequential()  
  28. model:add(nn.Reshape(1, 28, 28))  
  29. model:add(nn.MulConstant(1/256.0*3.2))  
  30. model:add(nn.SpatialConvolutionMM(1, 20, 5, 5, 1, 1, 0, 0))  
  31. model:add(nn.SpatialMaxPooling(2, 2 , 2, 2, 0, 0))  
  32. model:add(nn.SpatialConvolutionMM(20, 50, 5, 5, 1, 1, 0, 0))  
  33. model:add(nn.SpatialMaxPooling(2, 2 , 2, 2, 0, 0))  
  34. model:add(nn.Reshape(4*4*50))  
  35. model:add(nn.Linear(4*4*50, 500))  
  36. model:add(nn.ReLU())  
  37. model:add(nn.Linear(500, 10))  
  38. model:add(nn.LogSoftMax())  
  39.   
  40. model = require('weight-init')(model, 'xavier')  
  41.   
  42. criterion = nn.ClassNLLCriterion()  
  43.   
  44. --model = model:cuda()  
  45. --criterion = criterion:cuda()  
  46. --trainset.data = trainset.data:cuda()  
  47. --trainset.label = trainset.label:cuda()  
  48. --validationset.data = validationset.data:cuda()  
  49. --validationset.label = validationset.label:cuda()  
  50.   
  51. sgd_params = {  
  52.    learningRate = 1e-2,  
  53.    learningRateDecay = 1e-4,  
  54.    weightDecay = 1e-3,  
  55.    momentum = 1e-4  
  56. }  
  57.   
  58. x, dl_dx = model:getParameters()  
  59.   
  60. step = function(batch_size)  
  61.     local current_loss = 0  
  62.     local count = 0  
  63.     local shuffle = torch.randperm(trainset.size)  
  64.     batch_size = batch_size or 200  
  65.     for t = 1,trainset.size,batch_size do  
  66.         -- setup inputs and targets for this mini-batch  
  67.         local size = math.min(t + batch_size - 1, trainset.size) - t  
  68.         local inputs = torch.Tensor(size, 28, 28)--:cuda()  
  69.         local targets = torch.Tensor(size)--:cuda()  
  70.         for i = 1,size do  
  71.             local input = trainset.data[shuffle[i+t]]  
  72.             local target = trainset.label[shuffle[i+t]]  
  73.             -- if target == 0 then target = 10 end  
  74.             inputs[i] = input  
  75.             targets[i] = target  
  76.         end  
  77.         targets:add(1)  
  78.         local feval = function(x_new)  
  79.             -- reset data  
  80.             if x ~= x_new then x:copy(x_new) end  
  81.             dl_dx:zero()  
  82.   
  83.             -- perform mini-batch gradient descent  
  84.             local loss = criterion:forward(model:forward(inputs), targets)  
  85.             model:backward(inputs, criterion:backward(model.output, targets))  
  86.   
  87.             return loss, dl_dx  
  88.         end  
  89.   
  90.         _, fs = optim.sgd(feval, x, sgd_params)  
  91.   
  92.         -- fs is a table containing value of the loss function  
  93.         -- (just 1 value for the SGD optimization)  
  94.         count = count + 1  
  95.         current_loss = current_loss + fs[1]  
  96.     end  
  97.   
  98.     -- normalize loss  
  99.     return current_loss / count  
  100. end  
  101.   
  102. eval = function(dataset, batch_size)  
  103.     local count = 0  
  104.     batch_size = batch_size or 200  
  105.       
  106.     for i = 1,dataset.size,batch_size do  
  107.         local size = math.min(i + batch_size - 1, dataset.size) - i  
  108.         local inputs = dataset.data[{{i,i+size-1}}]--:cuda()  
  109.         local targets = dataset.label[{{i,i+size-1}}]:long()--:cuda()  
  110.         local outputs = model:forward(inputs)  
  111.         local _, indices = torch.max(outputs, 2)  
  112.         indices:add(-1)  
  113.         local guessed_right = indices:eq(targets):sum()  
  114.         count = count + guessed_right  
  115.     end  
  116.   
  117.     return count / dataset.size  
  118. end  
  119.   
  120. max_iters = 30  
  121.   
  122. do  
  123.     local last_accuracy = 0  
  124.     local decreasing = 0  
  125.     local threshold = 1 -- how many deacreasing epochs we allow  
  126.     for i = 1,max_iters do  
  127.         local loss = step()  
  128.         print(string.format('Epoch: %d Current loss: %4f', i, loss))  
  129.         local accuracy = eval(validationset)  
  130.         print(string.format('Accuracy on the validation set: %4f', accuracy))  
  131.         if accuracy < last_accuracy then  
  132.             if decreasing > threshold then break end  
  133.             decreasing = decreasing + 1  
  134.         else  
  135.             decreasing = 0  
  136.         end  
  137.         last_accuracy = accuracy  
  138.     end  
  139. end  
  140.   
  141. testset.data = testset.data:double()  
  142. eval(testset) 

你可能感兴趣的:(深度学习- 用Torch实现MNIST手写数字识别)