在上文已经讲到,Keras在构建模型方面,尤其是串联结构的模型时,使用Sequential无疑是一种比较好的选择,但是随着深度学习的不断发展,面对多种多样的模型,尤其是像GoogleNet等带有Inception结构的模型,仅仅是并联的结构是无法满足实际的需要,这种并联的网络结构往往对应着多个输出,这种时候我们往往需要选择更加通用的Functional模型,因为其的广泛性与通用性,在很多开源项目上面使用的就是这种以Model为类名的函数式模型。
本篇将会以CIFAR-10数据集的一系列操作为时间线,来学习Functional模型。
Input
from keras.layers import Input
inputs=Input(shape=(28,28,1),batch_shape=(None,28,28,1))
这里首先导入了keras中Input模块,这里的Input和TensorFlow中的占位符placeholder很相近,都是模型的输入结点,但是这里与TensorFlow不一样的地方是,这里可以设置batch_shape,而TensorFlow只能在训练的时候手动更改或者给出更多的代码才能实现动态的变化。
shape:输入形状。
batch_shape:输入加入第一维的batch_size之后的形状。
name:结点名。
dtype:输入数据类型名。
Model
from keras.models import Model
x = Input(shape=(32,))
y = Dense(16, activation='softmax')(x)
model = Model(inputs=x, outputs=y)
这里导入的是函数式模型的Model类,然后创建函数式模型对象。
inputs:模型第一层,即输入层。
outputs:模型最后一层,即模型输出层。
Model().save_weights
Model().load_weights
file_path='./cifarCnnModel.h5'
model.save_weights(filepath=file_path,overwrite=True)
model.load_weights(filepath=file_path,by_name=False)
这里进行的是模型权重的保存于重载,保存和重载的文件为HDF5文件,其文件后缀名为.h5。HDF5指一种为存储和处理大容量科学数据设计的文件格式及相应库文件。其具有一系列优异特性,使其特别适合进行大量科学数据的存储和操作,如它支持非常多的数据类型,灵活,通用,跨平台,可拓展,高效的I/O性能,几乎支持无限量(高达EB)的文件存储等。因HDF5优良的特性,这里模型权重保存方式选择了h5为后缀名的保存和重载方式。
filepath:模型权重保存位置。
overwrite:模型是否进行静默重写,默认为True。
by_name:默认为False,按照拓扑结构来重载模型,若为True,则通过结点名来加载模型。
TensorBoard
from keras.callbacks import TensorBoard
TensorBoard(log_dir='./logs',histogram_freq=0,
write_graph=True,write_images=False)
这里是TensorFlow网络的可视化的展示方法,TensorBoard即这个网络结构展示工具,这里的功能比原生的更加丰富,里面所有的参数都有默认值,一般而言,只写一个TensorBoard()
也能看到结果,这个回调函数将日志信息写入TensorBoard,可以动态的观察训练和测试指标的图像以及不同层的激活直方图。启动TensorBoard的方式也比较简单,即在命令行中使用如下指令:
tensorboard --logdir='./logs'
log_dir:保存日志文件地址。
histogram_freq:计算每个层激活值直方图频率,0为不计算。
write_graph:是否可视化图。
write_images:是否将权重以图片得形式可视化。
ModelCheckpoint
from keras.callbacks import ModelCheckpoint
file_path='epoch{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5'
ModelCheckpoint(filepath=file_path,monitor='val_loss',verbose=0,
save_best_only=False,save_weights_only=False,mode='auto',period=1)
该回调函数将在每个之后保存模型至file_path,file_path可以是格式化的字符串,里面的占位符将会被epoch
值和传入的loss
值以及val_loss
值所填入。本例代码filepath
值为epoch{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5
,则会生成对应epoch和loss以及验证集loss的不同批次的多个文件。
filename:字符串,保存模型的路径。
monitor:需要监视的值。每次保存模型,如果varbose>0,都会打印出监视值的变化信息。
verbose:0或者1,表示信息展示模式,0表示不展示。
save_best_only:当设置为True时,将只保存在验证集上性能最好的模型。
mode:‘auto’,‘min’,'max’之一,在save_best_only=True
时决定性能最佳模型的评判标准,例如,当监测值为val_acc时
,模式应为max
,当监测值为val_loss
时,模式应为min
。在auto
模式下,评价标准则由被监测值的名字自行推断。
save_weights_only:是否仅保存模型权重,否的话不仅保存模型权重,还要保存整个模型的其它信息(包括模型结构、配置信息等)。
period:CheckPoint之间的间隔epoch数。
ReduceLROnPlateau
from keras.callbacks import ReduceLROnPlateau
ReduceLROnPlateau(monitor='val_loss',factor=0.1,patience=10,
verbose=0,mode='auto',epsilon=0.0001,cooldown=0,min_lr=0)
该方法用于当评价 指标不在提升时,减小学习率,当学习停滞时,减少2倍或10倍的学习率常常能获得较好的结果。该回调函数检测monitor
指标的情况,如果在patience
个epoch中看不到模型性能的提升,则减少学习率。
monitor:被检测的量。
factor:每次减少学习率的因子,lr=lr*factor
。
patience:epoch数量内检测monitor变化情况。
verbose:0或者1,表示信息展示模式,0表示不展示。
mode:类似ModelCheckpoint。
epsilon:阈值,用来确定是否进入监测值的“平原区”。
cool down:学习率减少后,会经过多少个epoch才重新进行正常操作。
min_lr:学习率的下限。
EarlyStopping
from keras.callbacks import EarlyStopping
EarlyStopping(monitor='val_loss',min_delta=0,patience=0,verbose=0,mode='auto')
当监测值不再改善时,该回调函数将中止训练。
monitor:需要检测的量。
min_delta:监测值的最小变化。
patience:当early stop被激活(如发现loss相比上一个训练没有下降),经过patience
个epoch后停止训练。
verbose:信息展示模式。
mode:监测值优化模式。
from keras.datasets import cifar10
def get_random_data():
(X_train,Y_train),(X_test,Y_test)=cifar10.load_data()
print("train_data: image:{},label:{}".format(X_train.shape,Y_train.shape))
print("test_data: image:{},label:{}".format(X_test.shape,Y_test.shape))
return X_train,Y_train,X_test,Y_test
if __name__=='__main__':
X_train,Y_train,X_test,Y_test=get_random_data()
在正式开始之前,推荐先运行上述代码,这个数据集有162MB,直接用代码自带的下载方式或者在浏览器上下载都会比较慢,可以先把下载链接赋值下载使用下载器来下载会快很多,然后把文件放在用户目录下的.keras/datasets/下面即可,运行之后可以看到如下结果。完整代码链接。
train_data: image:(50000, 32, 32, 3),label:(50000, 1)
test_data: image:(10000, 32, 32, 3),label:(10000, 1)
我们可以从所打印的信息可以看出来,训练集的样本数量为50000张、测试集的样本数量为10000张,每张图片的像素值为32*32,且为RGB三通道的彩色图像。我们可以在下一个步骤看到部分数据集的样貌。
def show_data_graph(X_test,Y_test):
f,a=plt.subplots(15,15,figsize=(10,10))
for i in range(15):
for j in range(15):
a[i][j].imshow(X_test[i*15+j])
a[i][j].axis('off')
def data_format(X_train,X_test,Y_train,Y_test):
#数据格式转化
X_train,X_test=X_train.astype('float32'),X_test.astype('float32')
#数据标准化
X_train,X_test=X_train/255,X_test/255
#标签值哑编码
Y_train,Y_test=np_utils.to_categorical(Y_train),np_utils.to_categorical(Y_test)
return X_train,Y_train,X_test,Y_test
这里还是同上回MNIST一样,只做一个简单高效的数据处理方式,仅作一个标准化和标签哑编码的操作。在模型阶段,会增加一层BN层来进一步提高模型的拟合速率和准确率。
def get_model(inputs):
# 32
bn=BatchNormalization()(inputs)
# 28
cd1=Conv2D(filters=8, kernel_size=(5, 5),
strides=(1, 1), input_shape=(28, 28, 1), padding='valid', activation='relu')(bn)
# 14
cd2=MaxPooling2D(pool_size=(2, 2))(cd1)
# 12
cd3=Conv2D(filters=16, kernel_size=(3, 3),
strides=(1, 1), padding='valid', activation='relu')(cd2)
# 6
cd4=MaxPooling2D(pool_size=(2, 2))(cd3)
# 6
cd5=Conv2D(filters=64, kernel_size=(3, 3),
strides=(1, 1), padding='same', activation='relu')(cd4)
# 1
cd6=AveragePooling2D(pool_size=(6, 6))(cd5)
# 1
fl=Flatten()(cd6)
#model.add(Dense(units=100, activation='sigmoid'))
dp=Dropout(rate=0.25)(fl)
ds=Dense(units=10, activation='softmax')(dp)
model = Model(inputs=inputs,outputs=ds)
print(model.summary())
return model
因为还是初学,并且暂时没有特别好的主机,复杂模型这里就暂时掠过了,后面有时间在有针对的写一个,因而这里仅仅在上一篇的使用的模型的基础上增加了一层BN层,具体模型结构、参数以及参数数量如下图所示:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 32, 32, 3) 0
_________________________________________________________________
batch_normalization_1 (Batch (None, 32, 32, 3) 12
_________________________________________________________________
conv2d_1 (Conv2D) (None, 28, 28, 8) 608
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 8) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 12, 12, 16) 1168
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 6, 6, 16) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 6, 6, 64) 9280
_________________________________________________________________
average_pooling2d_1 (Average (None, 1, 1, 64) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 64) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 64) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 650
=================================================================
Total params: 11,718
Trainable params: 11,712
Non-trainable params: 6
_________________________________________________________________
None
def train():
log_dir='./log'
X_train,Y_train,X_test,Y_test=get_random_data()
#show_data_graph(X_test, Y_test)
#数据集划分
X_train, Y_train, X_test, Y_test=data_format(X_train,X_test,Y_train,Y_test)
#Input
inputs=Input(shape=(32,32,3),batch_shape=(None,32,32,3))
model=get_model(inputs)
# 模型训练
# 指定回调函数
logging=TensorBoard(log_dir=log_dir)
checkpoint=ModelCheckpoint(log_dir+'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
monitor='val_loss',save_best_only=True,mode='min',
save_weights_only=True,period=1)
reduce_lr=ReduceLROnPlateau(monitor='val_loss',factor=0.1,patience=3,verbose=1)
early_stopping=EarlyStopping(monitor='val_loss',min_delta=0,patience=10,verbose=1)
# 指定训练方式
model.compile(loss='categorical_crossentropy',
optimizer='adam', metrics=['accuracy'])
if os.path.exists('./'+log_dir+'train_weights.h5'):
model.load_weights('./'+log_dir+'train_weights.h5')
# 模型开始训练
train_history = model.fit(x=X_train, y=Y_train,callbacks=[logging,checkpoint,reduce_lr,early_stopping],
validation_split=0.2, epochs=10, batch_size=300, verbose=2)
model.save_weights(log_dir+'train_weights.h5')
if __name__=='__main__':
train()
输出信息打印如下:
Epoch 1/10
- 72s - loss: 1.6097 - acc: 0.4052 - val_loss: 1.5535 - val_acc: 0.4400
Epoch 2/10
- 71s - loss: 1.5648 - acc: 0.4277 - val_loss: 1.5213 - val_acc: 0.4549
Epoch 3/10
- 72s - loss: 1.5268 - acc: 0.4402 - val_loss: 1.4615 - val_acc: 0.4742
Epoch 4/10
- 74s - loss: 1.4914 - acc: 0.4581 - val_loss: 1.4462 - val_acc: 0.4769
Epoch 5/10
- 82s - loss: 1.4552 - acc: 0.4729 - val_loss: 1.3914 - val_acc: 0.4953
Epoch 6/10
- 77s - loss: 1.4308 - acc: 0.4812 - val_loss: 1.3770 - val_acc: 0.5106
Epoch 7/10
- 74s - loss: 1.4105 - acc: 0.4899 - val_loss: 1.3486 - val_acc: 0.5189
Epoch 8/10
- 75s - loss: 1.3933 - acc: 0.4990 - val_loss: 1.3324 - val_acc: 0.5309
Epoch 9/10
- 77s - loss: 1.3739 - acc: 0.5087 - val_loss: 1.3188 - val_acc: 0.5339
Epoch 10/10
- 75s - loss: 1.3549 - acc: 0.5155 - val_loss: 1.2931 - val_acc: 0.5389
准确说,这里是模型训练到13次的结果,因为一开始的3次,博主尝试了模型的保存与重载,因而这里打印的信息只会有到10次的内容。从上面的打印信息可以看出来,在这个时候模型的准确率仅为0.53,但是随着模型迭代的逐渐增加,模型的准确率应该会有所提升,但是最终应该不会升的太高,博主估计0.6就已经不错了,因为本次模型只是一个简单的CNN模型,并不能表达数量多且复杂的语义信息的判断,因而不会产生相对准确的结果。另外由于回调函数的使用,会在当前目录下生成日志文件和模型权重文件,我们可以简单的看一下目录结构。
在本篇里面由于增加了几个回调函数的API调用,并且增加了模型权重的保存重载功能,所以在结构上要比上一篇要完整很多,也相对增加了一定的参考价值,这里还有很大的优化和完善空间,若有机会会在后面的文章中再对它进行介绍与说明。
scores = model.evaluate(X_test, Y_test)
print(scores[1])
这里仅对测试集准确率作一个简单的打印。迭代23次之后的模型测试集准确率结果为0.5831
。
当你在进行数据统计分析,模型建立遇到困难的时候,那么请点开这个链接吧或者保存下面图片,打开淘宝立即看见:
https://shop163287636.taobao.com/?spm=a230r.7195193.1997079397.2.b79b4e98VwGtpt