基于空洞卷积神经网络识别Fashion-MNIST

一、空洞卷积神经网络介绍

空洞卷积(atrous convolution)又叫扩张卷积(dilated convolution),其实就是向卷积层引入了一个称为“扩张率(dilation rate)”的新参数,这个参数定义了卷积核处理数据时各值的间距。

1. 空洞卷积与标准卷积的区别

对于一个尺寸为 3 x 3 的标准卷积,卷积核大小为 3 x 3 ,卷积核上共包含9个参数,在卷积计算时,卷积核中的元素会与输入矩阵上对应位置的元素进行逐像素的乘积并求和。而空洞卷积与标准卷积相比,多了扩张率这一个参数,扩张率控制了卷积核中相邻元素间的距离,扩张率的改变可以控制卷积核感受野的大小。尺寸为 3 x 3 ,扩张率分别为 1,2,4 时的空洞卷积分别如 图1,图2,图3所示。

在这里插入图片描述

2.空洞卷积的感受野

空洞卷积通过引入扩张率这一参数使得同样尺寸的卷积核获得更大的感受野。相应地,也可以使得在相同感受野大小的前提下,空洞卷积比普通卷积的参数量更少。

空洞卷积的感受野计算方式与标准卷积大同小异。由于空洞卷积实际上可以看作在标准卷积核内填充’0’,所以我们可以将其想象为一个尺寸变大的标准卷积核,从而使用标准卷积核计算感受野的方式来计算空洞卷积的感受野大小。对于卷积核大小为 k ,扩张率为 r 的空洞卷积,感受野 F 的计算公式为:F=k+(k-1)(r-1)

卷积核大小 k = 3 ,扩张率 r = 2 时,计算方式如图4所示。
在这里插入图片描述

3.空洞卷积的参数量

当卷积核大小为3,扩张率为2时,通过一层空洞卷积后就可以得到大小为5 x 5的感受野,那么我们计算一下参数量:

1 x 1 x 3 x 3 = 9

二、项目背景

Fashion-MNIST是一个替代MNIST手写数字集的图像数据集,包括一个包含60,000个示例的训练集和一个包含10,000个示例的测试集。每个示例都是一个28x28灰度图像,与来自10个类的一个标签相关联。 它比MNIST更复杂,因此可以更好地表示神经网络的实际性能,并且可以更好地表示您将在现实世界中使用的数据集。

本项目使用paddle搭建CNN,用于Fashion-MNIST数据集的图像分类。
在这里插入图片描述

三、项目方案

该项目可以分为数据准备、模型建立以及使用训练集进行训练与使用测试集测试模型效果。

针对卷积神经网络的建立,将分别建立常用的卷积神经网络与基于空洞卷积的卷积神经网络。

在这里插入图片描述

四、代码实现

# import 导入模块
import paddle
import paddle.nn as nn
from paddle.io import Dataset
from paddle.vision.transforms import functional as F
from paddle.vision.transforms import RandomRotation
from paddle.vision import transforms
import matplotlib
import matplotlib.pyplot as plt
import PIL.Image as Image
import numpy as np
import pandas as pd
import cv2
import os
import time
import copy
import shutil
import zipfile
import random
def unzip_files(file_path,unzip_path):
    zipFile = zipfile.ZipFile(file_path)
    try:
        for file in zipFile.namelist():
            zipFile.extract(file, unzip_path)
    except:
        pass
    finally:
        zipFile.close()
fd_data = "./data/"    # data文件夹
zip_file_path = "./data/data145250/fashion-mnist_train.zip"    # 训练数据
unzip_files(os.path.normpath(zip_file_path),os.path.normpath(fd_data)) 
zip_file_path = "./data/data145250/fashion-mnist_test_data.zip" #测试数据
unzip_files(os.path.normpath(zip_file_path),os.path.normpath(fd_data)) 
train_csv_path = "./data/fashion-mnist_train.csv"
test_csv_path = "./data/fashion-mnist_test_data.csv"
train_csv = pd.read_csv(os.path.normpath(train_csv_path))
test_csv=pd.read_csv(os.path.normpath(test_csv_path))
test_csv.head()
IDpixel1pixel2pixel3pixel4pixel5pixel6pixel7pixel8pixel9...pixel775pixel776pixel777pixel778pixel779pixel780pixel781pixel782pixel783pixel784
00000000098...10387560000000
11000000000...34000000000
22000000145399...0000635331000
33000000000...13712614001332242225600
44000000000...0000000000

5 rows × 785 columns

plt.figure(figsize=(6, 6))
for idx in range(100):
    xy = train_csv.iloc[idx].values[1:].reshape(28,28)
    plt.subplot(10, 10, idx+1)
    # 展示图片
    plt.imshow(xy, cmap='gray')
    plt.xticks([]); plt.yticks([])

在这里插入图片描述

from paddle.io import DataLoader, Dataset
from PIL import Image
label_list = ["T恤","裤子","套头衫","连衣裙","大衣","凉鞋","衬衫","运动鞋","包","短靴"] 
# 自定义数据集完成数据读取
class MyDataset(Dataset):
    def __init__(self, img, label):
        super(MyDataset, self).__init__()
        self.img = img
        self.label = label
    
    def __getitem__(self, index):
        img = self.img[index]
        # 对数据集进行归一化
        return img/255, int(self.label[index])

    def __len__(self):
        return len(self.label)

# 训练集
train_dataset = MyDataset(
    train_csv.iloc[:-1000, 1:].values.reshape(59000, 28, 28).astype(np.float32), 
    paddle.to_tensor(train_csv.label.iloc[:-1000].values.astype(np.float32))
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

# 验证集
val_dataset = MyDataset(
    train_csv.iloc[-1000:, 1:].values.reshape(1000, 28, 28).astype(np.float32), 
    paddle.to_tensor(train_csv.label.iloc[-1000:].values.astype(np.float32))
)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)

# 测试集
test_dataset = MyDataset(
    test_csv.iloc[:, 1:].values.reshape(10000, 28, 28).astype(np.float32),
    paddle.to_tensor(np.zeros((test_csv.shape[0])))
)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

常用卷积网络结构展示
在这里插入图片描述

model = paddle.nn.Sequential(
    paddle.nn.Conv2D(in_channels=1,
                out_channels=16,
                kernel_size=3,
                stride=1,
                padding=1,),
    paddle.nn.ReLU(),
    nn.AvgPool2D(
                kernel_size=2,
                stride=2,
            ),
    
    paddle.nn.Conv2D(16,32,3,1,0),
    paddle.nn.ReLU(),
    paddle.nn.AvgPool2D((2, 2)),

    paddle.nn.Flatten(),
    paddle.nn.Linear(32*6*6, 256),
    paddle.nn.ReLU(),
    paddle.nn.Linear(256, 128),
    paddle.nn.ReLU(),
    paddle.nn.Linear(128, 10),

)

paddle.summary(model, (64, 1, 28, 28))
W0804 12:31:54.877988   134 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0804 12:31:54.882761   134 gpu_resources.cc:91] device: 0, cuDNN Version: 7.6.


---------------------------------------------------------------------------
 Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================
   Conv2D-1      [[64, 1, 28, 28]]     [64, 16, 28, 28]         160      
    ReLU-1       [[64, 16, 28, 28]]    [64, 16, 28, 28]          0       
  AvgPool2D-1    [[64, 16, 28, 28]]    [64, 16, 14, 14]          0       
   Conv2D-2      [[64, 16, 14, 14]]    [64, 32, 12, 12]        4,640     
    ReLU-2       [[64, 32, 12, 12]]    [64, 32, 12, 12]          0       
  AvgPool2D-2    [[64, 32, 12, 12]]     [64, 32, 6, 6]           0       
   Flatten-1      [[64, 32, 6, 6]]        [64, 1152]             0       
   Linear-1         [[64, 1152]]          [64, 256]           295,168    
    ReLU-3          [[64, 256]]           [64, 256]              0       
   Linear-2         [[64, 256]]           [64, 128]           32,896     
    ReLU-4          [[64, 128]]           [64, 128]              0       
   Linear-3         [[64, 128]]            [64, 10]            1,290     
===========================================================================
Total params: 334,154
Trainable params: 334,154
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.19
Forward/backward pass size (MB): 19.79
Params size (MB): 1.27
Estimated Total Size (MB): 21.25
---------------------------------------------------------------------------






{'total_params': 334154, 'trainable_params': 334154}
optimizer = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=0.0003)
criterion = paddle.nn.CrossEntropyLoss()
Train_Loss, Val_Loss = [], []
Train_ACC, Val_ACC = [], []
# 训练部分
for epoch in range(0, 10):
    

    #model.train()
    for i, (x, y) in enumerate(train_loader):
        pred = model(x.reshape((-1, 1, 28, 28)))
        loss = criterion(pred, y)
        Train_Loss.append(loss.item())
        loss.backward()
        optimizer.step()
        optimizer.clear_grad()
        Train_ACC.append((pred.numpy().argmax(1) == y.numpy()).mean())
    
    #model.eval()
    for i, (x, y) in enumerate(val_loader):
        pred = model(x.reshape((-1, 1, 28, 28)))
        loss = criterion(pred, y)
        Val_Loss.append(loss.item())
        Val_ACC.append((pred.numpy().argmax(1) == y.numpy()).mean())
    
plt.figure(figsize=(12,4))

plt.subplot(1,2,1)
plt.plot(Train_Loss,"ro-",label="Train loss")
plt.legend()

plt.xlabel("epoch")
plt.ylabel("Loss")

plt.subplot(1,2,2)
plt.plot(Train_ACC,"ro-",label="Train acc")
plt.xlabel("epoch")
plt.ylabel("acc")
plt.legend()
plt.show()
print("验证集最小损失:"+str(min(Val_Loss)))
print("验证集最高准确率:"+str(max(Val_ACC)))


在这里插入图片描述

验证集最小损失:0.1694793403148651
验证集最高准确率:0.85142578125

空洞卷积神经网络结构
在这里插入图片描述

model1 = paddle.nn.Sequential(
    paddle.nn.Conv2D(1,16,3,1,1,dilation=2),
    paddle.nn.ReLU(),
    nn.AvgPool2D(2,2),
    
    paddle.nn.Conv2D(16,32,3,1,0,dilation=2),
    paddle.nn.ReLU(),
    paddle.nn.AvgPool2D((2, 2)),

    paddle.nn.Flatten(),
    paddle.nn.Linear(32*4*4, 256),
    paddle.nn.ReLU(),
    paddle.nn.Linear(256, 128),
    paddle.nn.ReLU(),
    paddle.nn.Linear(128, 10),

)

paddle.summary(model1, (64, 1, 28, 28))
---------------------------------------------------------------------------
 Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================
   Conv2D-3      [[64, 1, 28, 28]]     [64, 16, 26, 26]         160      
    ReLU-5       [[64, 16, 26, 26]]    [64, 16, 26, 26]          0       
  AvgPool2D-3    [[64, 16, 26, 26]]    [64, 16, 13, 13]          0       
   Conv2D-4      [[64, 16, 13, 13]]     [64, 32, 9, 9]         4,640     
    ReLU-6        [[64, 32, 9, 9]]      [64, 32, 9, 9]           0       
  AvgPool2D-4     [[64, 32, 9, 9]]      [64, 32, 4, 4]           0       
   Flatten-2      [[64, 32, 4, 4]]        [64, 512]              0       
   Linear-4         [[64, 512]]           [64, 256]           131,328    
    ReLU-7          [[64, 256]]           [64, 256]              0       
   Linear-5         [[64, 256]]           [64, 128]           32,896     
    ReLU-8          [[64, 128]]           [64, 128]              0       
   Linear-6         [[64, 128]]            [64, 10]            1,290     
===========================================================================
Total params: 170,314
Trainable params: 170,314
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.19
Forward/backward pass size (MB): 15.29
Params size (MB): 0.65
Estimated Total Size (MB): 16.14
---------------------------------------------------------------------------






{'total_params': 170314, 'trainable_params': 170314}
optimizer = paddle.optimizer.Adam(parameters=model1.parameters(), learning_rate=0.0001)
criterion = paddle.nn.CrossEntropyLoss()
Train_Loss, Val_Loss = [], []
Train_ACC, Val_ACC = [], []
# 训练部分
for epoch in range(0, 10):
    

    #model.train()
    for i, (x, y) in enumerate(train_loader):
        pred = model1(x.reshape((-1, 1, 28, 28)))
        loss = criterion(pred, y)
        Train_Loss.append(loss.item())
        loss.backward()
        optimizer.step()
        optimizer.clear_grad()
        Train_ACC.append((pred.numpy().argmax(1) == y.numpy()).mean())

    #model.eval()
    for i, (x, y) in enumerate(val_loader):
        pred = model1(x.reshape((-1, 1, 28, 28)))
        loss = criterion(pred, y)
        Val_Loss.append(loss.item())
        Val_ACC.append((pred.numpy().argmax(1) == y.numpy()).mean())
    
plt.figure(figsize=(12,5))

plt.subplot(1,2,1)
plt.plot(Train_Loss,"ro-",label="Train loss")
plt.legend()
plt.xlabel("epoch")
plt.ylabel("Loss")

plt.subplot(1,2,2)
plt.plot(Train_ACC,"ro-",label="Train acc")
plt.xlabel("epoch")
plt.ylabel("acc")
plt.legend()

plt.show()
print("验证集最小损失:"+str(min(Val_Loss)))
print("验证集最高准确率:"+str(max(Val_ACC)))

在这里插入图片描述

验证集最小损失:0.1694793403148651
验证集最高准确率:0.953125
l=[]
for i in range(10000):
    z=test_csv.iloc[i, 1:].values.reshape((28, 28))
    l.append(z)
test = paddle.to_tensor(np.array(l),dtype='float32')
pre1=model(test.reshape((-1, 1, 28, 28)))
pre2=model1(test.reshape((-1, 1, 28, 28)))
yy1=paddle.argmax(pre1,1)
yy2=paddle.argmax(pre2,1)
import random
i=random.randint(0,10000)
print("常用卷积网络测试结果:"+label_list[yy1[i]])
print("空洞卷积网络测试结果:"+label_list[yy2[i]])
z=test_csv.iloc[i, 1:].values.reshape((28, 28))
plt.imshow(z, cmap='gray')
常用卷积网络测试结果:包
空洞卷积网络测试结果:包





<matplotlib.image.AxesImage at 0x7f2db30a2ed0>

在这里插入图片描述

五、总结

一般来说,在深度神经网络中增加感受野并且减少计算量的方法是下采样。但是下采样牺牲了空间分辨率和一些输入的信息。

空洞卷积一方面增大了感受野可以检测分割大目标,另一方面相较于下采样增大了分辨率可以精确定位目标。

当设置不同dilation rate时,感受野就会不一样,也即获取了多尺度信息

参考资料:

https://zhuanlan.zhihu.com/p/89425228

https://mp.weixin.qq.com/s/zqPdPvjT_RkoSFNSmOvUQA

https://blog.csdn.net/csyifanZhang/article/details/108145078

此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐