计算机系统应用教程网站

网站首页 > 技术文章 正文

「周末AI课堂」卷积之上的新操作|机器学习你会遇到的“坑”

btikc 2024-10-28 13:01:59 技术文章 10 ℃ 0 评论





卷积和池化在Keras中的使用


卷积操作在局部相关的数据中通过权重共享获得更好的表示,所以我们完全可以把它当作一个单独的层,在keras中,分别提供了一维卷积,二维卷积和三维卷积,以及它们的一些变体,比如cropping,transpose,separable,我们在这里主要介绍最基本也是最常见的二维卷积,并从图像的角度去解释参数的意义。

from keras.layers import Conv2D

Conv2D(filters=32,kernel_size=(3,3),strides=(1,1),\

padding='same',activation='relu',dilation_rate=1)


这里面的filters参数就是使用多少个卷积核,也决定了经过这层以后图像的深度。kernel_size决定了卷积核的大小,strides则是每次移动的步长,它可以是整数,表示在两个方向每次移动相同的步长,也可以是元组,分别表示在两个方向的不同步长。activation是激活函数,我们在卷积之后需要将提取的特征做非线性处理。

padding参数可以为‘valid’和‘same’,这里需要详细解释一下,valid对应的是有效卷积,same对应的是相同卷积。有效卷积是在说,卷积核在移动过程中,它访问的区域永远将整个卷积核包住,换而言之,如果出现了无法整除的情况,比如7*7的图像,2*2的卷积核,步长为2,那么有效卷积就存在无法访问的区域;相同卷积则是用零像素填充了边界,使得无论图像本身和卷积核、步长这三者无论大小是多少,其输出永远和输入的尺寸相同。但这样会造成边界附近因为零元素的存在提取的特征就变弱。

最后一个参数是空洞卷积的参数,控制其其膨胀率。空洞卷积指的是,在不增加参数的前提下,将原有的卷积核扩大。原本的卷积操作是3*3的卷积核作用在3*3的区域内,而设置dilation_rate=2后,空洞卷积则会作用在5*5的区域内,一定程度上扩大了特征范围。(关于这部分内容,我会在后续课程详细介绍)

池化层从原理上和实现上都较为简单,我们集中在二维的平均池化和最大池化操作上:

from keras.layers import

MaxPooling2D,AveragePooling2DMaxPooling2D(pool_size=(2, 2),padding='valid')

AveragePooling2D(pool_size=(2, 2),padding='valid')


其中pool_size参数是指定需要缩小的比例,设置为(2,2)是指将原来的图像的长和宽都缩减为原来的一半。padding参数的作用与卷积层一样。


研究方案和方法


我们尝试讨论以下几个问题:

  • 在参数数量不变的前提下,使用卷积能否提高图像识别的准确率?
  • 卷积一定要配合池化使用么?


我们仍然采用MNIST数据集做训练,沿用以前的结构(或者为了更好地看出效果,我们可以自行使得网络更深),将训练的结果保存。此时,我们要更加注意模型的性能,而非优化的问题。

卷积提高图片识别性能


卷积操作是为了获得一个更好的表示,我们要比较两个模型的表示性能,就要尽可能的减小优化带来的影响,我们尽可以把优化过程理解为对模型潜力的挖掘,如果不能保证优化过程的良好,我们就无法真正比较模型的优劣。同时,我们需要保证两种模型的可训练参数大致相同,如果需要证明卷积真的有效,最好是卷积模型的参数小于普通模型的参数,但是性能还会比普通模型更优。

我们曾在上一节见识到了Batch Normalization的强大性能,所以我们对普通模型涉及的全连接层添加BN层,并将其写作一个函数:

from keras.layers import Input

from keras.models import Model

from keras.layers import Dense,Activation,Flatten

from keras.layers import BatchNormalization as BN

def Dense_bn(x,units):

x = Dense(units)(x)

x =BN()(x)

x =Activation('relu')(x)

return x


在这里我们将全连接层,BN层,激活函数层写作了一层,所以我们的模型可以很快的写为:

from keras.layers import Conv2D,MaxPooling2D

def normal_model():

x= Input(shape=(28*28,))

x2= Dense_bn(x,1000)

x3=Dense_bn(x2,512)

x4=Dense_bn(x3,256)

y=Dense(10,activation='softmax')(x4)

model = Model(inputs=x, outputs=y)

model.compile(optimizer='SGD',\

loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)


可以看出,这里包含了两个隐层,可训练参数如下:

Total params: 1,438,482

Trainable params: 1,434,946

Non-trainable params: 3,536


为了保证优化,最好对卷积层也做Batch Normalization,我们仍然将其编写为一个层:

def Conv2D_bn(x,filters,length,width,\

padding='same',strides=(1, 1)):

x = Conv2D(filters, (length, width),\

strides=strides,padding=padding)(x)

x = BN()(x)

x = Activation('relu')(x)

return x


我们在此基础上搭建一个包含两个卷积层的模型:

def conv_model():

x= Input(shape=(28,28,1))

x2= Conv2D_bn(x,64,3,3)

x3= Conv2D_bn(x2,32,3,3)

x4=Flatten()(x3)

x5=Dense_bn(x4,32)

y=Dense(10,activation='softmax')(x5)

model = Model(inputs=x, outputs=y)

model.compile(optimizer='SGD',\

loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)


模型的可训练参数如下:

Total params: 822,794

Trainable params: 822,538

Non-trainable params: 256


我们可以发现,我们搭建的卷积神经网络的参数小于普通模型的参数,前者的模型复杂度应该小于后者。

接下来,我们为了保证快速检验模型的优劣,简单选取10000个样本,因为60000个样本优化过于缓慢。同时在这10000个样本中划分一部分到验证集观察其效果。对于这两个不同的模型,单纯的比较收敛快慢是毫无意义的,我们最后需要获得在验证集上的表现,即准确率。

import matplotlib.pyplot as plt

import seaborn as sns

model_1=normal_model()

his1=model_1.fit(X_train_normal,train_labels,\

batch_size=128,validation_split=0.3,\

verbose=1,epochs=10)

model_2=conv_model()

his2=model_2.fit(X_train_conv,train_labels,batch_size=128,\ validation_split=0.3,verbose=1,\

epochs=10)

w2=his2.history

w1=his1.history

sns.set(style='whitegrid')

plt.plot(range(10),w1['acc'],'b-.',label='Normal train')

plt.plot(range(10),w2['acc'],'r-.',label='CNN train')

plt.plot(range(10),w1['val_acc'],'b-',label='Normal test')

plt.plot(range(10),w2['val_acc'],'r-',label='CNN test')

plt.xlabel('epochs')

plt.ylabel('accuracy')

plt.legend()plt.show()




如图,我们可以看到即便普通全连接模型参数远大于CNN,但几乎在每一个epochs上,训练和测试准确率均不如CNN表现优异,平均看来,CNN的准确率要比全连接模型高出三个百分点,我们已经有理由说,CNN是比全连接神经网络在MNIST上更为优异的模型。


池化操作的用处


我们在上述的模型中并未加入池化操作,如果我们加入池化,会不会使得CNN表现更加优异呢?以及平均池化和最大池化效果是否会相同?

我们在第一层卷积操作中加入一个最大池化层,使得最后输出的尺寸变为原来的一半(面积变为1/4):

def conv_model_pooling():

x= Input(shape=(28,28,1))

x2= Conv2D_bn(x,64,3,3)

x3=MaxPooling2D(2)(x2)

x4= Conv2D_bn(x3,32,3,3)

x5=Flatten()(x4)

x6=Dense_bn(x5,32)

y=Dense(10,activation='softmax')(x6)

model = Model(inputs=x, outputs=y)

model.compile(optimizer='SGD',loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)


与上述训练一致,我们可以得到:



如图,我们可以看到加上最大池化操作的CNN的表现介于普通的全连接和不使用池化的CNN之间,并且验证集的准确率非常接近于原来的CNN。

同样的,我们可以使用平均池化操作,训练得到每个epochs的准确率,并添加到上图中:



如图,黑线和绿线几乎重合,平均池化和最大池化在这个例子中起到的作用是一致的。

那么在这个例子中,池化操作的意义在哪里呢?我们可以注意到添加最大池化的CNN可训练参数的个数变为了:

Total params: 220,682

Trainable params: 220,426

Non-trainable params: 256


最大池化使得模型的参数数量大大减少,却还能得到不错的性能。事实上,在实践中,池化的基本作用是假设了图像的平移不变性,提高了网络的统计效率,而对于准确率不一定会有提升。



作者:唐僧不用海飞丝

如需转载,请后台留言,遵守转载规范

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表