Keras简单应用cifar10

安装

pip install keras

backend:后端引擎,import keras时会提示当前使用的是哪一个backend

注:backend现在有tensorflowCNTKTheano

使用以下语句可以改变当前脚本使用的backend

1
2
3
import os

>>> os.environ['KERAS_BACKEND']='theano'

注:Keras APITensorflow的官方前端

Example:利用CNN实现cifar10小图片分类

1.先来简单了解几个问题

CNN(卷积神经网络)是什么?

从此明白了卷积神经网络

卷积层的使用

Conv2D:二维卷积层,即对图像的空域卷积。该层对二维输入进行滑动窗卷积,当使用该层作为第一层时,应提供input_shape参数。例如input_shape = (128,128,3)代表128*128的彩色RGB图像(data_format='channels_last'

model.add(Conv2D(32, (3, 3), padding='same', input_shape=(32, 32, 3)))

32表示filter即滤波器也就是卷积核数量,(3, 3)表示卷积核大小,这里需要重点解释下,为什么input_shape=(32, 32, 3)(即三通道),而filter却只是(3, 3)而不是(3, 3, 3),其实很多文章为了易于理解可能会将filter描述为三维的(包括上面那篇讲CNN的文章),其实卷积核是二维的,假设输入图片宽度为width:W,高度为height:H,通道数为D,卷积核个数为M;则对于D个通道而言,是在每个通道上分别执行二维卷积,然后将D个通道加起来,得到该位置的二维卷积输出,对于RGB三通道而言,就是在R,G,B三个通道上分别使用对应的每个通道上的kernel_size*kernel_size大小的核去卷积每个通道上的W*H的图片,然后将三个通道卷积得到的输出相加,得到M个二维卷积输出结果,在有padding的情况下,能保持输出图片大小和原来的一样,因此是:output(w, h, m) 。

注:也就是说卷积操作完成后输出的 out_channels ,取决于卷积核的数量

相应地也就容易理解,为什么keras.layers.convolutional.Conv2D的参数kernel_size支持的传入形式为:单个整数或由两个整数构成的list/tuple,卷积核的宽度和长度。如为单个整数,则表示在各个空间维度的相同长度。

相应的还有Conv1D、Conv3D,详情看keras文档

如何设定卷积核数量,大小,步长

trail and error

为什么要有激活函数

如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机Perceptron)。

如果使用的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。

如何选择激活函数

以后再去研究,这里先给出一般性结论:

  1. 除非在二分类问题中,否则请小心使用sigmoid函数。
  2. 可以试试 tanh,不过大多数情况下它的效果会比不上 ReLUMaxout
  3. 如果你不知道应该使用哪个激活函数, 那么请优先选择Relu
  4. 如果你使用了Relu, 需要注意一下Dead Relu问题, 此时你需要仔细选择 Learning rate, 避免出现大的梯度从而导致过多的神经元 “Dead” 。
  5. 如果发生了Dead Relu问题, 可以尝试一下leaky ReLU,ELURelu变体, 说不定会有惊喜。

为什么要有池化层

关于池化操作的简单过程在从此明白了卷积神经网络文章中有过一定介绍,目的就是在尽可能保留图片空间信息的前提下,降低图片的尺寸,增大卷积核感受野,提取高层特征,同时减少网络参数量,预防过拟合

2.使用Keras Sequential顺序模型建立CNN

指定输入数据的尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def quality_classify_model():
model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', input_shape=(32, 32, 3))) # 卷积层
model.add(Activation('relu')) # 激活函数
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2))) # 最大池化
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten()) # 全连接层
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5)) # 随机抛弃一半,避免过拟合
model.add(Dense(num_classes))
# 激活函数我们用softmax,这个函数适合多分类任务,sigmoid适合二分类任务
model.add(Activation('softmax'))

# initiate RMSprop optimizer
opt = keras.optimizers.RMSprop(learning_rate=0.0001, decay=1e-6)

# Let's train the model using RMSprop
model.compile(loss='categorical_crossentropy',
optimizer=opt,
metrics=['accuracy'])
return model

模型编译

1
2
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])

模型编译的代码同样在quality_classify_model()函数中。

optimizer优化器和loss损失函数是keras模型编译时,两个必需的参数,可以使用keras内置的优化器,或实例化一个优化器,自己指定参数值,厉害的话自己编写优化器。其中SGD(随机梯度下降优化器),是性价比最好的算法。

损失函数(loss function)是用来估量模型的预测值f(x)与真实值Y的不一致程度,它是一个非负实值函数,通常使用L(Y, f(x))来表示,损失函数越小,模型的鲁棒性就越好。

注意:softmax使用的即为交叉熵损失函数,binary_cossentropy为二分类交叉熵损失,categorical_crossentropy为多分类交叉熵损失,当使用多分类交叉熵损失函数时,标签应该为多分类模式,即使用one-hot编码的向量。

模型训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
batch_size = 64
num_classes = 10
epochs = 100
data_augmentation = True
num_predictions = 20

def train():
# 数据载入
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# x_train shape: (50000, 32, 32, 3)
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples') # 50000
print(x_test.shape[0], 'test samples') # 10000

# to_categorical就是将类别向量转换为二进制(只有0和1)的矩阵类型表示
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
# 生成训练数据
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

# 开始训练
model = quality_classify_model()
hist = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test), shuffle=True)

因为使用的损失函数为多分类交叉熵损失,所以需要使用to_categorical将类别向量转换为二进制(只有0和1)的矩阵类型表示,即将原有的类别向量转换为独热编码的形式。

to_categorical函数功能例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from keras.utils.np_utils import *
#类别向量定义
b = [0,1,2,3,4,5,6,7,8]
#调用to_categorical将b按照9个类别来进行转换
b = to_categorical(b, 9)
print(b)

执行结果如下:
[[1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1.]]

fit函数

1
2
fit( x, y, batch_size=32, epochs=10, verbose=1, callbacks=None, validation_split=0.0, 
validation_data=None, shuffle=True, class_weight=None, sample_weight=None, initial_epoch=0)

这里简单说下fit函数各个参数的含义,x是输入,y是目标(标签),batch_size指定进行梯度下降时每个batch包含的样本数;epochs是训练终止时的epoch值;validation_data形式为(X,y)的tuple,是指定的验证集;shuffle一般为布尔值,表示是否在训练过程中随机打乱输入样本的顺。具体可以参考官方文档

以上训练模型训练完成后的结果是:训练集准确率:0.8232,验证集准确率:0.7981

简单CNN数据增强

由于深度学习的效果很大程度上依赖于数据量,因此如果固定模型不变,效果不佳时一个很重要的优化方案就是增加数据量,但有时候我们无法简单地获取到新的图像数据,比如这个cifar10数据集,就这么多数据,再找不太现实,那怎么办呢?有一种增加数据量的方法叫做数据增强。

Keras自带一种生成相似图像数据的方式,即使用ImageDataGenerator类。简单地说就是这个类可以对原始图像进行水平/竖直移动一定范围、水平/垂直翻转图像、放大图像一定范围等等,达到生成新的同类图像的目的,这种新生成的图像还是属于同样的类别,比如你把一张猫的图像平移15%的距离,它还是一只猫,但是与原图像又不是完全一样,因此也是提升了数据丰富程度的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def train_new():
# 数据载入
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# 多分类标签生成
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
# 生成训练数据
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

train_data_gen = ImageDataGenerator(
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=True,
)
train_data_gen.fit(x_train)
model = quality_classify_model()
hist = model.fit_generator(
train_data_gen.flow(x_train, y_train, batch_size=batch_size),
epochs=epochs,
validation_data=(x_test, y_test),
workers=4)

# 保存模型
model.save('cifar10_model.h5')
model.save_weights('cifar10_model_weight.hdf5')

# 查看准确率
hist_dict = hist.history
print('train acc:', hist_dict['accuracy'])
print('validation acc:', hist_dict['val_accuracy'])

对比一下上一节的train函数,就可以发现不同点,首先,对于训练数据,我们用ImageDataGenerator类返回了一个train_data_gen ,用来生成训练的数据,这个ImageDataGenerator中就包含了一些变化的方式,包括标准化、旋转角度、水平移动、竖直移动等,详情看官网文档

需要注意的是,我们只应该对训练数据进行数据增强,对于验证集,不要去变动。

在开始训练的时候,也从fit函数改成了fit_generator函数,这个函数才能接受ImageDataGenerator类返回的train_data_gen作为输入,也就是train_data_gen.flow()函数,其中设置了原始数据、批尺寸batch_size,这里同样是每一次处理的数据量,epochs还是原来那个epochs,也就是要完整训练多少轮。

经过以上数据增强模型训练后,训练集准确率:0.7701,验证集准确率:0.8087。

经过与上面的模型对比,训练集准确率反而下降了,但是验证集准确率有一点点提升,而且有一个显著的特点是使用数据增强后,验证集的准确率高于训练集的准确率。这里算是一个遗留的问题,后面不断学习再来解决。

保存模型

save()保存的模型结果,它既保持了模型的图结构,又保存了模型的参数

save_weights()保存的模型结果,它只保存了模型的参数,但并没有保存模型的图结构

HDF5格式文件保存的是 : Model weights
H5 格式文件保存的是:Model stuctureModel weights

保存模型时需要使用h5py库,Keras应该是默认安装了h5py,所以可以直接使用。

关于测试

1
2
3
scores = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

上面的代码中我们就有使用到x_test,y_test,作为validation_data参数值,这个参数是验证集的意思,即每轮训练之后使用该集合验证,根据验证结果程序不断调整,使得准确率越来越高。而evaluate函数是针对测试集的,如果验证集和测试集使用相同的集合,那得到的准确率当然也是一样的,也就说其实是没必要再进行测试的。所以当我们数据充足时,可以将数据分为三个集合,分别为:训练集、验证集和测试集。

参考文章:

作者:Cloudox_
链接:https://www.jianshu.com/p/946bdebeb6fc
来源:简书