首先,在众多深度学习框架中,我最开始上手的框架(因为那时候先接触的caffe,配置使用都太麻烦了)是Keras,什么叫做搞什么深度学习,不过是搭积木罢了,这句话真的太适合形容Keras了。Keras是一个高级的深度学习框架,是架设于tensorflow、Theano、CNTK三大深度学习框架之上的,可以设置切换后台为其中之一,不过目前比较多的应该是用tensorflow作为后台,tensorflow好像也提供了很多Keras的接口。Theano是很老的,现在停止维护了,CNTK是微软的,受众相对较窄。
然后说下数据集mnist,tensorflow自带了mnist数据集,可以通过下面代码加载数据集:
from tensorflow.examples.tutorials.mnist import *
mnist=input_data.read_data_sets("/tmp/mnist",one_hot=True)
返回的mnist有两个成员train和test,其下又有images和labels分别表示图像和标签,上述one_hot表示用二进制编码标签,后面用到categorical_crossentropy作为损失函数的时候就会用到。mnist数据集包含了55000个训练样本和10000个测试样本。加载了数据集之后要注意数据的维度顺序,Keras的数据大部分是默认为4D张量的,用tensorflow作为后台的时候,数据为张量,其维度顺序为[样本数,高度,宽度,通道数](用Theano作为后端的时候,其顺序为[样本数,通道数,高度,宽度]),所以需要我们调整数据的维度顺序:
train_X=train_X.reshape((train_X.shape[0],28,28,1))
test_X=test_X.reshape((test_X.shape[0],28,28,1))
接下来就是构建一个神经网络了,这下就要充分体现Keras搭积木的特性了,Keras构建一个神经网络的方式有两种,一种是序贯模型,一种是函数模型。这里就先介绍第一种序贯模型。
序贯模型是多个网络层的线性堆叠,也就是“一条路走到黑”。构建神经网络的时候通过向Sequential模型传递一个layer的list来构造该模型,比如:
from keras.models import Sequential
from keras.layers import Dense, Activation
model = Sequential([
Dense(32, units=784),
Activation('relu'),
Dense(10),
Activation('softmax')
])
这样就构建了一个包含全连接层、激活层、全连接层、激活层的网络结构。其次,也可以通过add()来添加层,比如:
model = Sequential()
model.add(Dense(32, input_shape=(784,)))
model.add(Activation('relu'))
这里我们采用add()来添加层:
model=Sequential()
# 28
model.add(Conv2D(32,(3,3),activation='relu', input_shape=(28,28,1),padding="valid")) # 26
model.add(Conv2D(32,(3,3),activation='relu',padding="valid")) # 24
model.add(MaxPool2D(pool_size=(2,2))) # 12
model.add(Dropout(0.5)) # 12
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 10
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 8
model.add(MaxPooling2D(pool_size=(2, 2))) # 4
model.add(Dropout(0.5))
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(64,activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adadelta',metrics=['accuracy'])
最后一句compile是用来编译模型的,这里需要指定损失函数、优化算法和评估指标等,其中损失函数和优化算法是必须指定的。搭好积木之后就是要让网络跑起来训练,这时候就会用到fit函数了,fit函数参数挺多的,但是只有两个是必须指定的,就是输入数据和标签,输入数据的shape必须和模型的输入的shape一致,标签则和输出一致,fit还可以指定训练的回合数、数据块的大小等信息,其函数原型如下:
fit(self, 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)
这里每个变量的含义是可以在Keras的文档中查找的,说几个,verbose是训练过程中会有信息输出显示,这里定义其显示的形式,有三种形式:0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录;callbacks是一个回调函数的list,主要用于训练过程中一些操作;validation_split是将x和y切成训练集和验证集的验证集占比,如果设置了validation_data就用validation_data的。
完整的代码显示如下:
from tensorflow.examples.tutorials.mnist import *
from keras.models import *
from keras.layers import *
from keras.callbacks import ModelCheckpoint, LearningRateScheduler,History, BaseLogger,Callback,EarlyStopping, ReduceLROnPlateau
import numpy as np
# os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 加载数据集
mnist=input_data.read_data_sets("/tmp/mnist",one_hot=True)
train_X=mnist.train.images
train_Y=mnist.train.labels
test_X=mnist.test.images
test_Y=mnist.test.labels
train_X=train_X.reshape((55000,28,28,1))
test_X=test_X.reshape((test_X.shape[0],28,28,1))
print("type of train_X:",type(train_X))
print("size of train_X:",np.shape(train_X))
print("train_X:",train_X)
print("type of train_Y:",type(train_Y))
print("size of train_Y:",np.shape(train_Y))
print("train_Y:",train_Y)
print("num of test:",test_X.shape[0])
# 配置模型结构
model=Sequential()
model.add(Conv2D(32,(3,3),activation='relu',input_shape=(28,28,1), padding="valid")) # 26
model.add(Conv2D(32,(3,3),activation='relu',padding="valid")) # 24
model.add(MaxPool2D(pool_size=(2,2))) # 12
model.add(Dropout(0.5)) # 12
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 10
model.add(Conv2D(64, (3, 3), activation='relu',padding="valid")) # 8
model.add(MaxPooling2D(pool_size=(2, 2))) # 4
model.add(Dropout(0.5))
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4
model.add(Conv2D(128, (3, 3), activation='relu',padding="same")) # 4
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(64,activation="relu"))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adadelta',metrics=['accuracy'])
# 训练模型
epochs=200
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.3, verbose=1, patience=16, min_lr=5e-4)
#save at each epoch if the validation decreased
checkpointer = ModelCheckpoint(filepath= 'best_model_ep{}.h5'.format(epochs), verbose=1, monitor='val_loss', mode='auto', save_best_only=True)
es = EarlyStopping(monitor='val_loss', patience=64)
history = model.fit(train_X, train_Y, epochs=epochs, batch_size=32, verbose=1, shuffle=True, validation_split=0.1, callbacks=[checkpointer, reduce_lr, es])
# 用测试集去评估模型的准确度
accuracy=model.evaluate(test_X,test_Y,batch_size=20)
print('\nTest accuracy:',accuracy[1])
save_model(model,'last_model_ep{}.h5'.format(epochs))
在实验中,我将训练集的十分之一用于做验证集,这里用ModelCheckpoint回调函数来保存模型,检测验证集上的loss,将loss最低的权重保存为best,将最后一次epoch的权重保存为last,EarlyStopping则是另一个回调函数,用于检测验证集的loss是否下降,如果一直不下降超过一定回合数,那就提前停止训练;ReduceLROnPlateau适用于调整训练的学习率的,如果验证集上的loss持续了几个回合都没有下降了就减少学习率。
训练完成后用model.evaluate()来评估模型的性能,函数原型为:
evaluate(self, x, y, batch_size=32, verbose=1, sample_weight=None)
最后截个图显示下训练结果:
最终测试集上的准确率是0.9941,应该还是比较可以的分数吧。