初学者的CNN搭建示例(torch,cifar10数据集)

来源:互联网 发布:s5700交换机ip mac绑定 编辑:程序博客网 时间:2024/05/22 10:47

前言:

  • 之前一直眼高手低,哦是懒,也就是偶尔翻翻书,不怎么摸代码更不用说project了。
  • 本硕都是电气,硕士快结束喜欢上AI这东西,半路出家就搞起了机器学习,原因呢很偶然。
  • 三月份还没写过二分查找,五月面试的时候面试官夸我的python用的溜、数据结构算法底子不错、而后实现了cifar10数据集测试集75%(还没有对数据做一些白化、旋转、拉伸等处理,如果做的话准确率应该会更高),看来多动手管用。
  • 后期可能去用TensorFlow多一些,在此,对之前torch中搭建CNN遇到的问题做一个总的回顾吧。
  • 本文可能适合类似我的初学者,大神请跳过或者指教下,ok,开始。

  • 前言
  • torch的教程入门
  • torch教程之后的继续
    • 数据的来源
    • trainset和testset的形成
    • CNN网络的搭建
    • 参数的选择和训练
  • Gungun结语

torch的教程入门

  1. torch的语言lua就我目前用到的来看,我觉得还是蛮人性化和简单的。我的语言底子(基本没底子)也就会一丢丢python,看着网上的torch入门教程(请自行百度吧,很多)学的torch使用。
  2. 我记得有个系列教程的,http://blog.csdn.net/u010946556/article/details/51329208这个是第一个,在文章末尾会有整个系列链接,应该是一共7篇,感谢这位博主,我跟着从头看到尾码了一下,对torch搭建CNN有了一个大致的了解。如果跟我一样基本小白的,可以跟这个博主教程走一遍,也花不了多少时间。

torch教程之后的继续

跟着上面的博主教程走完,可以看到你的代码成功运行,测试集准确率大概不到50%,进一步需要自己做了。

1.数据的来源

那个博主训练集只有10000个样本,所以直接去官网下载cifar-10数据集吧,我下载的是matlab版本的数据,文件后缀是.mat,然后torch读取此类型文件应该是要装一个包,就是下面代码中的‘matio’,至于如何安装,链接在此https://github.com/soumith/matio-ffi.torch/blob/master/README.md,感谢这位的教程帮了我忙。下面代码是load数据部分,手段可能很蠢(没啥经验,请原谅),但运行没毛病,这一点放心。

require 'nn'require 'paths'require 'optim'--载入cifar 10,注意labels为0-9,而不是1-10,另外训练50000组,测试10000组local matio=require'matio'source1= matio.load('/Users/xuhy/Downloads/cifar-10-batches-mat/data_batch_1.mat')--训练部分1a1=source1.data:reshape(10000,3,32,32);b1=source1.labels;source2= matio.load('/Users/xuhy/Downloads/cifar-10-batches-mat/data_batch_2.mat')--训练部分2a2=source2.data:reshape(10000,3,32,32);b2=source2.labels;source3= matio.load('/Users/xuhy/Downloads/cifar-10-batches-mat/data_batch_3.mat')--训练部分3a3=source3.data:reshape(10000,3,32,32);b3=source3.labels;source4= matio.load('/Users/xuhy/Downloads/cifar-10-batches-mat/data_batch_4.mat')--训练部分4a4=source4.data:reshape(10000,3,32,32);b4=source4.labels;source5= matio.load('/Users/xuhy/Downloads/cifar-10-batches-mat/data_batch_5.mat')--训练部分5a5=source5.data:reshape(10000,3,32,32);b5=source5.labels;--测试部分source6=matio.load('/Users/xuhy/Downloads/cifar-10-batches-mat/test_batch.mat')--测试部分数据a6=source6.data:reshape(10000,3,32,32);b6=source6.labels;--再次注意labels为0-9,至此数据载入完成

这个地方自己可以输出原始data看看结构,没记错的话原始应该为10000*1024的tensor,这里reshape成了10000*3*32*32,就是RGB三通道。
然后需要将数据进行拼接,因为训练集被分成了5个部分,另外要注意,labels的数据是0~9而不是1~10,可以自己输出测试下,这关系到后面的loss function,所以特意给了自己标注。

2.trainset和testset的形成

自己可以手动输出几组看下数据类型,这过程会熟练对tensor的操作,在上面load和reshape之后,进行拼接,形成trainset和dataset。

train_data=torch.cat(a1,a2,1);train_data=torch.cat(train_data,a3,1);train_data=torch.cat(train_data,a4,1);train_data=torch.cat(train_data,a5,1);--得到train_data,Tensor(50000,3,32,32)train_labels=torch.cat(b1,b2,1);train_labels=torch.cat(train_labels,b3,1);train_labels=torch.cat(train_labels,b4,1);train_labels=torch.cat(train_labels,b5,1);--得到train_labels(50000,1),取值0~9test_data=a6;--test_data,Tensor(10000,3,32,32)test_labels=b6;--test_labels,(10000,1),取值0~9--构建trainset和testset,暂时没做validationset,后面如果做,训练集用40000个即可trainset={    size=50000,    data=(train_data:double()):clone(),--没加深拷贝之前,老是在第一个epoch上就内存占满卡死,    label=train_labels:clone()--同上,这个可能和lua的内存调用有关,传递的是引用不是数值,直接clone过来就OK了?}testset={    size=10000,    data=(test_data:double()):clone(),--同上,蜜汁尴尬啊,    label=test_labels:clone()--同上}

这里有两个值得注意的点:(1)使用cat进行拼接,参数取“1”、“2”应该是表示列方向拼接、行方向拼接,总之这里可以自己体会下,将两个10000*3*32*32的tensor拼接为20000*3*32*32的tensor应该怎么做;(2)另外就是这个深拷贝clone的现象,我也觉得很奇怪,因为时而出现内存炸了的情况有时候又没有,这里需要大神指点下了,就是去掉这个clone部分,在第一个epoch就死了,偶尔呢又没事,具体原因我也没摸清楚。至此训练集、测试集准备完成。

3.CNN网络的搭建

一开始使用的网络是最开始提及的教程系列的网络,后来在看到http://www.cnblogs.com/neopenx/p/4480701.html后对将网络简化为3个卷积层和3个池化层,最后展开softmax,去掉了全连接层,而且第一个采用maxpooling,第二个和第三个采用overlapped average pooling,效果可以好到75%准确率测试集。

但是有个问题,就是读者可以自己试下会看到结果对learning rate等参数初始化敏感,而且对输入数据做了标准化处理。

后续加上batch normalization结构后,去掉数据标准化过程(教程中有,自己走一遍教程的应该还记得),会发现结果对初始参数不怎么敏感,而且收敛速度变快了,很快就达到了测试集75%准确率,大概二三十个epoch?有点忘了。大家可以对比下有无BN层的情况。
关于BN层的理解和使用,链接我忘记了,自己百度去吧,如果没记错参数分别为(通道数、初始γ、初始β、true表示带缩放和偏置)?

-- 神经网络结构net=nn.Sequential()net:add(nn.SpatialConvolution(3,32,3,3))net:add(nn.SpatialBatchNormalization(32,1e-5,0.1,true))net:add(nn.ReLU())net:add(nn.SpatialMaxPooling(2,2,2,2))net:add(nn.SpatialConvolution(32,32,4,4))net:add(nn.SpatialBatchNormalization(32,1e-5,0.1,true))net:add(nn.ReLU())net:add(nn.SpatialAveragePooling(2,2,1,1))net:add(nn.SpatialConvolution(32,64,5,5))net:add(nn.SpatialBatchNormalization(64,1e-5,0.1,true))net:add(nn.ReLU())net:add(nn.SpatialAveragePooling(2,2,1,1))net:add(nn.View(64*6*6))net:add(nn.Linear(64*6*6,10))net:add(nn.LogSoftMax())criterion=nn.ClassNLLCriterion()

分类问题,这里用NLL作为loss function。

4.参数的选择和训练

本文开头的推荐教程系列使用了简单的sgd训练,熟悉深度学习的同学都知道,比较常用的是mini batch sgd,这个地方推荐一个链接http://blog.csdn.net/u012749168/article/details/52684794,本人就是在这之上调整了一下的,细心的同学可以发现其中的区别,我认为原来的代码是有一些问题的(?),比如求size这里原来的代码是-t,我这里是-(t-1),剩下的不同点,认真看了的都能发现。

另外这个链接给出了GPU也就是加上cuda的模式,直接跑会出现问题,因为链接中少了一个:cuda,没有对indices进行:cuda处理。
PS:我的和链接中的微微有点不同,但本质上是一致的,另外我这个版本在CPU跑了几次没有问题,之前没有加BN层时在GPU跑了几次,代码也应该没啥问题吧。

sgd_params={    learningRate=1.0,    weightDecay=5e-4,    momentum=0.5}x,dl_dx=net:getParameters()step=function(batch_size)    local current_loss=0    local count=0    local shuffle=torch.randperm(trainset.size)--打乱生成1-50000的随机组数    batch_size=batch_size or 200    for t=1,trainset.size,batch_size do        print(t)        local size=math.min(t+batch_size-1,trainset.size)-(t-1)        local inputs=torch.Tensor(size,3,32,32)        local targets=torch.Tensor(size)        for i=1,size do--生成batchsize样本            local input=trainset.data[shuffle[i+t-1]]            local target=trainset.label[shuffle[i+t-1]]            inputs[i]=input            targets[i]=target+1----------------        end        local feval=function(x_new)            if x ~=x_new then x:copy(x_new) end            dl_dx:zero()--梯度归零            local loss=criterion:forward(net:forward(inputs),targets)--求loss            net:backward(inputs,criterion:backward(net.output,targets))--两次backward,一次求梯度,一次更新权值            return loss,dl_dx        end        _, fs=optim.sgd(feval,x,sgd_params)--fs是一个loss function数值的table        count=count+1        current_loss=current_loss+fs[1]    end    return current_loss/countendeval=function(dataset,batch_size)    local count=0    batch_size=batch_size or 200    for i=1,dataset.size,batch_size do        local size=math.min(i+batch_size-1,dataset.size)-(i-1)        local inputs=dataset.data[{{i,i+size-1}}]        local targets=dataset.label[{{i,i+size-1}}]:long():add(1)------------------        local outputs=net:forward(inputs)--用计算好的模型计算输出        local _,indices=torch.max(outputs,2)--?        local guessed_right=indices:eq(targets):sum()        count=count+guessed_right    end    return count/dataset.sizeendmax_iters=66do    for i=1,max_iters do        local loss=step()        print(string.format( 'Epoch:%d Current loss:%4f', i,loss))        local accuracy=eval(trainset)        print(string.format( 'Accuracy on the trainset:%4f',accuracy))    endend--测试集数据标准化testset.data=testset.data:double()

这里解释一下几个函数:(1)step()这个函数,输入是batchsize,默认为200,我在GPU上跑的时候,选择的是128,也就是在下面的循环里是local loss=step(128),step()这个函数就是进行一次全样本也就是1个epoch训练,并返回loss值;(2)eval(dataset)这函数很直观了,评价一个dataset的accuracy。

PS:这里给个建议,代码最好不要复制过去使用,如果像我一样比较小白,一句话一句话自己敲进去,遇到不明白的函数或者表达,百度之然后翻阅下博客论文什么的,消除这个疑问,然后继续,当敲到最后一句代码且没什么疑问的时候,你会发现学到了不少东西,动手动脑思考,这也是给我自己的要求了。

Gungun结语

因为后续可能短时间没办法继续更新这部分了,本来后面的打算是有两个,一是对数据进行处理,包括拉伸旋转剪切等扩充数据集看下能把准确率提高多少,二是采用vgg、resnet等各种大佬结构练下。

有机会再说,先这样吧。