keras和pytorch实现CIFAR-10物体分类识别

前面第一篇文章我们实现了keras和pytorch的入门helloworld:

(12条消息) keras和pytorch深度学习框架的hello world!_caojianhua2018的博客-CSDN博客  https://blog.csdn.net/caojianhua2018/article/details/112339089

对使用keras和pytorch有了一定的认识。接下来我们基于lenet5为骨架的卷积神经网络来实现经典数据集CIFAR-10物体识别实践。

1.CIFAT-10数据集

CIFAR-10是一个小型的图片分类数据集,由6万张32x32彩色图像构成,每一张图都对应一个类别,不过这6万张图片总共也就10个类别。百说不如一图,如下显示的10个类别,包括飞机、汽车、鸟、猫、鹿、狗、青蛙、马、轮船和卡车,其中每个类别随机抽取了10张图。

在CIFAT-10数据集中,如果去下载的话会发现整个数据集由5个训练batches,和1个训练batch。每个batch由10000张图像构成。同时类别与类别之间是相互独立的,而且基本上每张图形中识别目标只有一个,就是当前的标签类别。这个数据集可以直接从网站上下载,除了CIFAR-10 外,还有扩展的 CIFAR-100 datasets数据集。链接地址为:

http://www.cs.toronto.edu/~kriz/cifar.html​www.cs.toronto.edu

 

由于这个数据集对于图像识别而言也是非常经典的数据集,在许多深度学习编程框架里都进行了封装,我们在使用的时候可以直接调用下载即可。不过很多情况下由于网速的缘故,下载时间会很长,所以可以直接从其官网上下载下来然后根据说明来加载数据。

下载地址: http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz

下载解压后目录显示如下:

加载数据:根据这个数据集的网站说明,使用python的pickle模块来处理,具体的函数为:

def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict

很明显,上述函数中只需要传入file文件路径和文件名就可以将数据处理为字典。将上述batch_1文件处理后,得到如下数据:

file = 'data/cifar-10-batches-py/data_batch_1'
data = unpickle(file)
print(data.keys())     #查看键名
print(data[b'batch_label'])   #查看该批数据名称
print(len(data[b'filenames']))  #查看filenames长度,这是每个图片的名称
print(len(data[b'labels']))   #查看labels标签长度,这是每个图片对应的标签名称
print(len(data[b'data']))     #查看data图片数据长度,data是每个图片所在的三通道二维像素矩阵

如果要准备训练集,则需要将5个data_batch合起来,组织成训练数据集和训练标签集:

#读取文件函数
def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict

#组织训练集数据和标签
train_data,train_labels=[],[]
for i in range(1,6):
    file = 'data/cifar-10-batches-py/data_batch_'+str(i)
    data = unpickle(file)
    for item,label in zip(data[b'data'],data[b'labels']):
        train_data.append(item)
        train_labels.append(label)

#组织测试集数据和标签
test_data,test_labels=[],[]
file = 'data/cifar-10-batches-py/test_batch'
data = unpickle(file)
for item,label in zip(data[b'data'],data[b'labels']):
    test_data.append(item)
    test_labels.append(label)

#将数据集列表转化为ndarray格式
x_train,y_train = np.array(train_data),np.array(train_labels)
x_test,y_test = np.array(test_data),np.array(test_labels)

#查看数据维度大小
print("训练集图像数据:",x_train.shape)
print("训练集标签数据:",y_train.shape)
print("测试集图像数据:",x_test.shape)
print("测试集标签数据:",y_test.shape)

执行后终端显示:

获得的图像数据size为3072,因为是3通道,每个通道图像尺寸为32x32,因此每个图像的像素点总数为3x32x32=3072。为便于后续的处理和显示,需要使用numpy的reshape函数将图像数据重新处理一下:

对图像数据进行reshape处理,同时进行归一化
x_train4 = x_train.reshape(x_train.shape[0],3,32,32).astype('float32')
x_train4_norm = x_train4/255

x_test4 = x_test.reshape(x_test.shape[0],3,32,32).astype('float32')
x_test4_norm = x_test4/255

#准备标签数据,进行编码处理
y_train_1hot = np_utils.to_categorical(y_train,10)
y_test_1hot = np_utils.to_categorical(y_test,10)

这样基本数据都准备完成了。不过因为既然是图像,我们应该先可视化一下看一下图像长什么样。有两种方案,一种是直接将上述的图像数据采用cv的imwrite方法保存到本地,另一种就是采用matplotlib来查看,我们选其中一个训练图像来看一下:

#定义显示图像函数
def pltshow(img,label,label_name):
    plt.title("label index:"+str(label)+"; label name:"+str(label_name))
    plt.imshow(img)
    plt.show()

#读取batches.meta元文件获得对应标签名
label_info = unpickle('data/cifar-10-batches-py/batches.meta')
label_names = label_info[b'label_names']
print(label_names)

#显示图像及其标签,x_train4_norm[0][0]是第一个样本的第1个通道图像,y_train[0]为第一个样本对应的标签
pltshow(x_train4_norm[0][0],y_train[0],label_names[y_train[0]])

执行后,显示目前标签序号从0到9分别对应名称为:

[b'airplane', b'automobile', b'bird', b'cat', b'deer', b'dog', b'frog', b'horse', b'ship', b'truck']

训练集里第一个样本图像显示如下:

还可以查看其他的:

很明显,因为像素个数少,同时又是单通道,图像特征并不是非常清晰,如果想看到彩色图则需要重新处理一下,如下代码实现:

#显示第一个样本的彩色图,x_train4[0]表示第一个图像数组
# 图像的存储格式一般为:[长,宽,通道数],需要先对处理过的图像矩阵数组格式变为整型
im = np.array(x_train4[0],dtype=int)
#上一步图像格式为[通道数,长,宽],需要使用transpose实现转置,格式变为[长,宽,通道数]
img = im.transpose((1,2,0))
# 绘制彩图
plt.title("label index:"+str(y_train[0])+"; label name:"+str(label_names[y_train[0]]))
plt.imshow(img)
plt.show()

执行后就可以绘制出第一个样本图像的彩色图了,同样的方式可以绘制其他样本图像了:

不过因为分辨率的缘故,图像并不清晰。此时我们试着将多个图绘制在同一个面上,这样效果就好许多:

#显示彩色图
for i in range(10):
    plt.figure(1)
    plt.subplot(2,5,i+1)
    # # 图像的存储格式一般为:[长,宽,通道数],需要先对处理过的图像矩阵数组格式变为整型
    im = np.array(x_train4[i],dtype=int)
    # #上一步图像格式为[通道数,长,宽],需要使用transpose实现转置,格式变为[长,宽,通道数]
    img = im.transpose((1,2,0))
    # 绘制彩图
    plt.title("index:"+str(y_train[i])+";name:"+str(label_names[y_train[i]]))
    plt.imshow(img)
plt.show()

执行后效果如下:

2. 基本的卷积神经网络模型结构

在选用卷积神经网络模型来实现图形分类检测时,主要依据Lecun大神提出的lenet-5卷积神经网络骨架,也就是由卷积层+全连接两大部分构成。卷积层用于提取图像的特征,然后将特征喂入全连接层开展学习训练。

首先可以设计一下网络结构:

卷积层包括:两次卷积+池化,第一次卷积采用32个卷积核,得到32个特征图,然后进行下采样池化,获得32张16x16的特征图;第二次卷积采用64个卷积核,得到64个特征图,接着池化处理,获得64张8x8的特征图。

全连接层:输入神经元数就是上述64张8x8特征图展平后的64x8x8=4096个特征点,隐层个数设置为1024,输出层为10个神经元,对应10个类别的概率。

卷积核个数、隐层个数都需要经过测试,有些取自于经验,有些参考其他网络模型。目前深度学习发展到AUTOML自动学习阶段,这些基本参数通过自动学习可以获取最优结构。

同时在网络模型中加入dropout、batch normalization、padding不变等优化策略,接下来就可以采用keras和pytorch实现卷积神经网络训练和预测。

3. 基于Keras实现CIFAR-10图像分类预测

keras属于tensorflow的高阶API,可以使用堆叠方式构建网络结构,如下实现:

#第二部分:构建网络模型
model = Sequential()
#1.第一个卷积层:输入图像为32x32x3,卷积核3x3,共32个卷积核,边界策略padding设置为same保持不变,激活函数采用relu
model.add(Conv2D(filters=32,
                 kernel_size=(3,3),
                 padding='same',
                 input_shape=(32,32,3),
                 activation='relu'))
#2.第一个池化层,大小为2x2
model.add(MaxPooling2D(pool_size=(2,2)))
#3.第二个卷积层:卷积核3x3,共64个卷积核,边界策略padding设置为same保持不变,激活函数采用relu
model.add(Conv2D(filters=64,
                 kernel_size=(5,5),
                 padding='same',
                 activation='relu'))
#4.第二个池化层,大小为2x2
model.add(MaxPooling2D(pool_size=(2,2)))
#5.采用dropout策略
model.add(Dropout(0.25))
#6.拉平特征体成一维向量
model.add(Flatten())
#7.搭建神经网络第一个隐层,120个节点,激活函数使用relu
model.add(Dense(1024,activation='relu'))
model.add(Dropout(0.25))
#8.搭建神经网络输出层,10个节点,激活函数采用softmax
model.add(Dense(10,activation='softmax'))
#查看网络结构图
print(model.summary())

执行后可以得到网络结构摘要如下:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 32, 32, 32)        2432      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 16, 16, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 16, 16, 64)        51264     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 64)          0         
_________________________________________________________________
dropout (Dropout)            (None, 8, 8, 64)          0         
_________________________________________________________________
flatten (Flatten)            (None, 4096)              0         
_________________________________________________________________
dense (Dense)                (None, 1024)              4195328   
_________________________________________________________________
dropout_1 (Dropout)          (None, 1024)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 10)                10250     
=================================================================
Total params: 4,259,274
Trainable params: 4,259,274
Non-trainable params: 0
_________________________________________________________________
None

Process finished with exit code 0

网络结构搭好后,下面可以开始编译和训练了。编译相对简单,设定loss计算方式和优化方法:

#第三部分:对网络模型进行编译
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

训练部分这里需要注意以下,由于从本地加载的cifar-10数据集前面在处理的时候将一张图像(3通道)共4096个点用(3,32,32)进行了reshape,我们也看到了实际的图像显示。但进入模型时模型中接收的数据shape则应该为(长,宽,通道数),所以需要使用numpy的transpose方法将原来的(通道数,长,宽)调整为(长,宽,通道数)。

# #第四部分:对网络模型进行训练
train_data = x_train4_norm.transpose((0,2,3,1))
test_data = x_test4_norm.transpose((0,2,3,1))

代码中transpose参数为一个元组,(0,2,3,1)中0表示图像个数位置,共5万个,该位置不能改变,后面的2,3,1中2和3表示图像的长和宽,1表示通道数。也就是需要使用transpose方法将训练集图像调整为【图像个数,图像长,图像宽,通道数】。同样的方式也需要对测试集进行调整。

然后在喂入模型的训练fit函数中进行训练:

model.fit(x=train_data,
          y=y_train_1hot,
          validation_split=0.2,
          epochs=10,
          batch_size=300,verbose=2)

参数中设定了10次迭代学习,运行后发现精度不算很高:

Epoch 9/10
134/134 - 35s - loss: 0.6824 - accuracy: 0.7622 - val_loss: 0.8277 - val_accuracy: 0.7163
Epoch 10/10
134/134 - 37s - loss: 0.6291 - accuracy: 0.7807 - val_loss: 0.7921 - val_accuracy: 0.7301

不过可以发现,由于参数量要比lenet5多,所以耗时也相对较长。如果想进一步提高精度,还可以增加卷积层,也就是增加网络的深度。这个我们放在后面的VGG和Googlenet实践中再来阐述。

最后使用测试集中的数据开展测试预测,调用model模型的predict_classes方法获得预测标签:

#第五部分:使用训练好的模型开展测试应用
print("预测结果:",model.predict_classes(test_data[:10]))
print("实际标签:",y_test[:10])

得到预测结果为:

预测结果: [3 8 8 0 6 6 1 6 3 1]
实际标签: [3 8 8 0 6 6 1 6 3 1]

效果还是不错的,在前10个预测样本中精度达到了100%。不过当样本增多时,就会发现误差开始增多了,例如取前30个预测样本预测时效果如下:

预测结果: [3 1 8 8 6 6 1 2 3 1 0 9 5 7 9 8 5 7 8 6 7 2 2 9 4 2 7 0 9 6]
实际标签:  [3 8 8 0 6 6 1 6 3 1 0 9 5 7 9 8 5 7 8 6 7 0 4 9 5 2 4 0 9 6]

30个样本,预测结果仅有23个正确,7个错误,预测精度接近80%。

4. 基于pytorch实现CIFAR-10分类预测

使用pytorch来实现CIFAR-10数据分类预测时,第一步也是需要获得数据集。

(1)加载数据集

可以使用torch封装好的下载来使用:

#下载数据集  
train_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR',        #存放目录为本地data-cifar文件夹下
                                           train=True,         #训练集
                                           transform=transforms.ToTensor(), #转换为tensor
                                           download=True)      #同意下载到本地目录下
test_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR',
                                           train=False,        #测试集
                                           transform=transforms.ToTensor(),
                                           download=True)

但速度也是超级的慢,所以我们还是需要先下载了数据集,再存到上述的data-CIFAR目录下。这样再运行上面这步获取训练集和测试集时,就会提示:

Files already downloaded and verified
Files already downloaded and verified

也就是说,上面的train_dataset和test_dataset两个数据dataset就准备好了。接下来就可以使用DataLoader模块实现动态的数据抽取加载。

#第一部分:下载数据集到本地
#下载数据集
train_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR',        #存放目录为本地data文件夹下
                                           train=True,         #训练集
                                           transform=transforms.ToTensor(), #转换为tensor
                                           download=True)      #同意下载到本地目录下
test_dataset = torchvision.datasets.CIFAR10(root='data-CIFAR',
                                           train=False,        #测试集
                                           transform=transforms.ToTensor(),
                                           download=True)
#装载数据,每批100组数据
train_loader = DataLoader(train_dataset,batch_size=100,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size=100,shuffle=False)

(2)了解数据

主要是了解数据集的准备情况,前面介绍数据集时我们已经知道了数据的内容和构成。这里我们可以再确认一下:

#第二部分:查看数据了解数据
# 由于DataLoader的策略是分批加载,每批100组数据,共500批次。
# 每批数据组由100张原始图像和100个标签对构成。
print("训练集数据组批次总数:",len(train_loader))
#训练集中包括imgs和labels
img_data,label_data = [],[]
for imgs,labels in train_loader:
    img_data.append(imgs)
    label_data.append(labels)

#查看原始图像信息
print("查看第一批图像数据维度:",img_data[0].shape)
print("第一批里第一张原始图数据维度:",img_data[0][0].shape)
print("第一批里第一个标签数据:",label_data[0][0])

#标签对应的类别名
classes={0:'airplane',1:'automobile',2:'bird',3:'cat',4:'deer',5:'dog',6:'frog',7:'horse',8:'ship',9:'truck'}
#绘制第一批10张图像
for i in range(10):
    img = np.array(img_data[0][i]).transpose((1,2,0))  #先进行transpose通道数后移,形成32x32x3维度
    plt.figure(1)
    plt.subplot(2,5,i+1)    #绘制2行5列单元格
    plt.imshow(img)         #每个单元格绘一幅图
    plt.title("name:{}".format(classes[int(label_data[0][i])])) #给每个图增加一个标签说明
plt.show()

运行后效果如下:

(3)构建卷积神经网络模型

这部分过程可以参考上一篇文章或者直接拿过来修改即可,基于上述网络结构的布局,代码参考如下:

#第三部分:创建卷积神经网络模型
class CONvNET(nn.Module):
    def __init__(self):
        super(CONvNET, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)   #第一个卷积层参数
        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)  #第二个卷积层参数
        self.fc1 = nn.Linear(4096, 120) #第一个全连接层参数
        self.fc2 = nn.Linear(120, 10)         #输出层参数

    def forward(self, x):
        x = F.relu(self.conv1(x)) #第一次卷积处理
        x = F.max_pool2d(x, 2, 2) #第一次池化
        x = F.relu(self.conv2(x)) #第二次卷积处理
        x = F.max_pool2d(x, 2, 2) #第二次池化
        x = x.view(x.size()[0],-1)  #展平处理
        x = F.relu(self.fc1(x))   #第一次全连接处理
        x = self.fc2(x)           #第二次全连接处理
        return F.log_softmax(x, dim=1)  #输出结果

(4)设定训练和验证过程函数

#第四部分:对训练集进行训练
def train(epoch,model,traindata,optimizer,criterion):
    '''
    :param model:网络模型
    :param data: 输入处理训练集
    :param optim: 优化器选择
    :param criterion: 计算loss方法
    :return:
    '''
    model.train()    #对模型进行训练
    for index, (data, target) in enumerate(traindata):
        #前向计算过程
        optimizer.zero_grad()  # 梯度先清零
        output = model(data)
        loss = criterion(output, target)

        #反向梯度优化过程
        loss.backward()  # 误差反向传播计算
        optimizer.step()  # 更新梯度

        if index % 100 == 0:
            # 保存训练模型
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, index * len(data), len(train_loader.dataset),
                       100. * index / len(train_loader), loss.item()))
#第五部分:使用验证集验证模型
def validate(model,testdata,criterion):
    '''
    :param model: 网络模型
    :param data: 验证集
    :param criterion: 计算loss方法
    :return:
    '''
    model.eval()
    running_loss = 0
    correct = 0
    for data,target in testdata:
        y_estimate = model(data)
        loss = criterion(y_estimate,target)
        running_loss+=loss.item()*data.size(0)
        pred = y_estimate.data.max(1,keepdim=True)[1]
        correct += pred.eq(target.data.view_as(pred)).cpu().sum()
    epoch_loss = running_loss / len(testdata)
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
        epoch_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))

(5)喂入训练集训练,并保存模型

#第六部分:设定超参数
#创建一个模型net对象,具有lenet5骨架结构
net = CONvNET()
#使用nn模块的交叉熵方法确定loss的计算标准
criterion = nn.CrossEntropyLoss(reduction='sum')
#采用nn模块的optim优化器中的Adam方法
optimizer = optim.Adam(net.parameters(), lr=1e-3)

#第七部分:训练模型并将模型保存下来
# for epoch in range(1,20):
#     train(epoch=epoch,model=net,traindata=train_loader,optimizer=optimizer,criterion=criterion)
torch.save(net, 'ownmodel.pkl')
print("training process end....")

(6)基于训练好的模型对测试集进行预测

model  = torch.load('ownmodel.pkl')
validate(model=model,testdata=test_loader,criterion=criterion)

最终预测精度接近70%:

下面选择测试集中的小部分数据进行模型验证

#8.选择小批数据预测,每批次4组数据
data_loader_test = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size = 4,
                                          shuffle = True)
#取出其中第一组数据,返回图像数据和标签
X_test, y_test = next(iter(data_loader_test))
#加载保存好的网络模型
model  = torch.load('ownmodel.pkl')
#将图像数据传入模型中进行预测
y_estimate = model(X_test)
#将预测tensor向量转化为ndarry
pred_y = torch.max(y_estimate, 1)[1].data.numpy()
#输出结果
print("该组图像预测数字为:",pred_y)
print("该组图像实际数字为:",y_test.numpy())

该组预测结果:

至此,我们使用keras和pytorch完成了CIFAR-10的分类识别,由于设计的卷积神经网络相对较浅,预测精度都不是太高。后续我们再尝试使用googlenet或VGG,使得目标识别精度提高。

上述两种框架CIFAR-10分类识别代码下载地址为:

caoln2003/deeplearning​gitee.com

下载后可以直接运行获得结果。如果有什么疑问或者表述不清楚的地方,欢迎下方留言交流

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值