3.5.1 使用HDF5格式保存模型
通常把Keras模型保存为HDF5格式,该文件包含模型结构、模型权重值及优化器状态。使用HDF5格式时,即使你不再有权访问创建模型的代码,也可以从该文件重新创建相同的模型。
继续以印第安人发生糖尿病的二元分类问题为例,创建一个小型深度学习模型,并将整个模型都保存到HDF5文件中。
> # 在当前目录中创建models文件夹 > dir.create('models',showWarnings = TRUE) > # # 导入数据集 > dataset <- read.csv('../data/pima-indians-diabetes.csv',skip = 9,header = F) > X = as.matrix(dataset[,1:8]) > y = dataset[,9] > # 定义及编译模型 > library(keras) > # 定义及编译模型 > library(keras) > model <- keras_model_sequential() > model %>% + layer_dense(units = 12,input_shape = c(8),kernel_initializer = 'uniform', activation = 'relu') %>% + layer_dense(units = 8,kernel_initializer = 'uniform',activation = 'relu') %>% + layer_dense(units = 1,kernel_initializer = 'uniform',activation = 'sigmoid') %>% + compile(loss='binary_crossentropy',optimizer = 'adam',metrics = c('accuracy')) > # 训练模型 > model %>% fit( + X, y, + batch_size = 10, + epochs = 100, + verbose = 0, + validation_split = 0.33 + ) > # 保存整个模型 > save_model_hdf5(model,'models/model.h5') > # 查看保存的文件 > list.files('models') [1] "model.h5"
运行完以上程序代码后,在models文件夹中生成了保存整个模型的model.h5文件,可以通过load_model_hdf5将其导入。
> # 加载HDF5文件 > new_model <- load_model_hdf5('models/model.h5') > # 利用加载的模型对X进行预测 > new_prediction <- predict(new_model,X) > # 利用之前训练好的模型对X进行预测 > prediction <- predict(model,X) > # 判断两次预测结果是否完全相同 > all.equal(new_prediction,prediction) [1] TRUE
我们还可以将整体模型导出为TensorFlow的SavedModel格式,SaveModel是TensorFlow对象的独立序列化格式。注意,model_to_save_model仅适用于TensorFlow1.14及以上版本。
> # 保存为SaveModel格式 > model_to_saved_model(model,'models/savemodel/') > list.files('models/savemodel/') [1] "assets" "saved_model.pb" "variables" > # 重新创建完全一致的模型 > new_model1 <- model_from_saved_model('models/savemodel/') > # 与之前的预测结果进行对比 > new_prediction1 <- predict(new_model1,X) > all.equal(new_prediction1,prediction) [1] TRUE
SaveModel在新文件夹savemodel中创建的文件包含如下内容。
- 一个包含模型权重的TensorFlow检查点。
- 一个包含基础TensorFlow图的SaveModel原型。保存单独的图形以进行预测、训练和评估。如果模型不是以前编译过的,则仅导出推理图。
- 模型的结构配置(如果存在的话)。
有时候,如果你只对结构感兴趣,则无须保存权重值或优化器。在这种情况下,可以通过get_config()方法得到模型的结构,该方法返回一个命名列表,使得可以从头开始初始化重新创建相同的模型,而无须获取先前模型在训练期间学到的任何信息。
> config <- get_config(model) > config {'name': 'sequential', 'layers': [{'class_name': 'Dense', 'config': {'name': 'dense', 'trainable': True, 'batch_input_shape': (None, 8), 'dtype': 'float32', 'units': 12, 'activation': 'relu', 'use_bias': True, 'kernel_initializer': {'class_name': 'RandomUniform', 'config': {'minval': -0.05, 'maxval': 0.05, 'seed': None}}, 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, 'kernel_regularizer': None, 'bias_regularizer': None, 'activity_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}}, {'class_name': 'Dense', 'config': {'name': 'dense_1', 'trainable': True, 'dtype': 'float32', 'units': 8, 'activation': 'relu', 'use_bias': True, 'kernel_initializer': {'class_name': 'RandomUniform', 'config': {'minval': -0.05, 'maxval': 0.05, 'seed': None}}, 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, 'kernel_regularizer': None, 'bias_regularizer': None, 'activity_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}}, {'class_name': 'Dense', 'config': {'name': 'dense_2', 'trainable': True, 'dtype': 'float32', 'units': 1, 'activation': 'sigmoid', 'use_bias': True, 'kernel_initializer': {'class_name': 'RandomUniform', 'config': {'minval': -0.05, 'maxval': 0.05, 'seed': None}}, 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, 'kernel_regularizer': None, 'bias_regularizer': None, 'activity_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}}]}
通过from_config()函数得到一个重新初始化的模型,该模型仅仅继承了之前模型的网络结构,并不包含模型训练得到的权重值(此时的权重值为初始化值)和优化器,如图3-25所示。
> reinitialized_model <- from_config(config) > summary(reinitialized_model)
图3-25 通过from_config()函数得到重新初始化的模型
现在,利用重新初始化的模型对训练数据集进行预测,并判断是否与最初模型model的预测结果一致。
> new_prediction2 <- predict(reinitialized_model,X) > all.equal(new_prediction2,prediction) [1] "Mean relative difference: 0.3864167"
从返回结果可知,两个结果是有差异的,原因在于我们重新初始化的模型只保留了模型结构,并不会保留模型状态(权重值和优化器)。
我们可以利用get_weights()函数得到模型权重值,返回一个数组列表。比如,在定义网络时,bias(偏置)项的初始化权重值默认为0。下面利用get_weights()函数查看model和reinitialized_model两者第一个隐藏层偏置项的权重值。
> # 查看第一个隐藏层的偏置项的权重值 > get_weights(model)[[2]] [1] -0.032459475 0.000000000 0.853002846 0.884543836 1.109097242 [6] 0.820603907 -0.349359781 0.788280964 -1.016711950 -0.009251177 [11] -0.999970675 -0.015671620 > get_weights(reinitialized_model)[[2]] [1] 0 0 0 0 0 0 0 0 0 0 0 0
可见,reinitialized_model仅仅继承了model的网络结构,并未保留model中的网络权重值,因为reinitialized_model每层中各神经元的偏置项的权重值均为0。可以利用set_weights()函数将reinitialized_model的权重值设置为与model的权重值一致。
> # 设置权重值 > weight <- get_weights(model) > set_weights(reinitialized_model,weight) > get_weights(reinitialized_model)[[2]] [1] -0.032459475 0.000000000 0.853002846 0.884543836 1.109097242 [6] 0.820603907 -0.349359781 0.788280964 -1.016711950 -0.009251177 [11] -0.999970675 -0.015671620 > new_prediction3 <- predict(reinitialized_model,X) > all.equal(new_prediction3,prediction) [1] TRUE
我们可以组合使用get_config()/from_config()和get_weights()/set_weights(),以相同状态重新创建模型。但是,与save_model_hdf5()不同,这将不包括训练配置和优化器。所以,如果想使用该模型进行训练,我们必须先使用compile()函数重新编译网络。
> # 直接编译会出错 > reinitialized_model %>% fit( + X, y, + batch_size = 10, + epochs = 100, + verbose = 0, + validation_split = 0.33) Error in py_call_impl(callable, dots$args, dots$keywords) : RuntimeError: You must compile your model before training/testing. Use `model.compile(optimizer, loss)`.
错误提示说明在利用reinitialized_model模型训练前需要利用compile()函数进行网络编译。
我们也可以通过save_model_weights_hdf5()函数将模型权重保存到磁盘中,并通过load_model_weights_hdf5()函数将其导入R。
> # 保存模型权重值到磁盘中 > save_model_hdf5(model,'models/model_weights.h5') > # 加载到R中 > new_model3 <- from_config(get_config(model)) > load_model_weights_hdf5(new_model3,'models/model_weights.h5')
新创建的new_models模型与reinitialized_model相同,注意,此模型的优化器也未保留。所以,最简单的保存模型的方法如下:
save_model_hdf5(model, "model.h5") new_model <- load_model_hdf5("model.h5")