【问题标题】:How to save Scikit-Learn-Keras Model into a Persistence File (pickle/hd5/json/yaml)如何将 Scikit-Learn-Keras 模型保存到持久性文件 (pickle/hd5/json/yaml)
【发布时间】:2017-03-16 17:10:22
【问题描述】:

我有以下代码,使用Keras Scikit-Learn Wrapper:

from keras.models import Sequential
from sklearn import datasets
from keras.layers import Dense
from sklearn.model_selection import train_test_split
from keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
from sklearn import preprocessing
import pickle
import numpy as np
import json

def classifier(X, y):
    """
    Description of classifier
    """
    NOF_ROW, NOF_COL =  X.shape

    def create_model():
        # create model
        model = Sequential()
        model.add(Dense(12, input_dim=NOF_COL, init='uniform', activation='relu'))
        model.add(Dense(6, init='uniform', activation='relu'))
        model.add(Dense(1, init='uniform', activation='sigmoid'))
        # Compile model
        model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
        return model

    # evaluate using 10-fold cross validation
    seed = 7
    np.random.seed(seed)
    model = KerasClassifier(build_fn=create_model, nb_epoch=150, batch_size=10, verbose=0)
    return model


def main():
    """
    Description of main
    """

    iris = datasets.load_iris()
    X, y = iris.data, iris.target
    X = preprocessing.scale(X)

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=0)
    model_tt = classifier(X_train, y_train)
    model_tt.fit(X_train,y_train)

    #--------------------------------------------------
    # This fail
    #-------------------------------------------------- 
    filename = 'finalized_model.sav'
    pickle.dump(model_tt, open(filename, 'wb'))
    # load the model from disk
    loaded_model = pickle.load(open(filename, 'rb'))
    result = loaded_model.score(X_test, Y_test)
    print(result)

    #--------------------------------------------------
    # This also fail
    #--------------------------------------------------
    # from keras.models import load_model       
    # model_tt.save('test_model.h5')


    #--------------------------------------------------
    # This works OK 
    #-------------------------------------------------- 
    # print model_tt.score(X_test, y_test)
    # print model_tt.predict_proba(X_test)
    # print model_tt.predict(X_test)


# Output of predict_proba
# 2nd column is the probability that the prediction is 1
# this value is used as final score, which can be used
# with other method as comparison
# [   [ 0.25311464  0.74688536]
#     [ 0.84401423  0.15598579]
#     [ 0.96047372  0.03952631]
#     ...,
#     [ 0.25518912  0.74481088]
#     [ 0.91467732  0.08532269]
#     [ 0.25473493  0.74526507]]

# Output of predict
# [[1]
# [0]
# [0]
# ...,
# [1]
# [0]
# [1]]


if __name__ == '__main__':
    main()

如代码中所述,它在这一行失败:

pickle.dump(model_tt, open(filename, 'wb'))

出现此错误:

pickle.PicklingError: Can't pickle <function create_model at 0x101c09320>: it's not found as __main__.create_model

我怎样才能绕过它?

【问题讨论】:

  • classifier 函数之外定义create_model 函数。你有什么理由需要这个词法闭包吗?
  • @juanpa.arrivillaga:我需要在里面有classifier,因为我需要传递参数。看到这个问题stackoverflow.com/questions/40393629/…

标签: python scikit-learn persistence pickle keras


【解决方案1】:

接受的答案太复杂了。您可以在.h5 文件中完全保存和恢复模型的各个方面。直接来自Keras FAQ

您可以使用model.save(filepath) 将 Keras 模型保存到单个 HDF5 文件将包含:

  • 模型的架构,允许重新创建模型
  • 模型的权重
  • 训练配置(损失、优化器)
  • 优化器的状态,允许在您停止的地方恢复训练。

然后您可以使用keras.models.load_model(filepath) 重新实例化您的模型。 load_model 还将负责使用保存的训练配置编译模型(除非模型一开始就从未编译过)。

以及对应的代码:

from keras.models import load_model

model.save('my_model.h5')  # creates a HDF5 file 'my_model.h5'
del model  # deletes the existing model

# returns a compiled model identical to the previous one
model = load_model('my_model.h5')

【讨论】:

  • 嗯,问题/答案来自 2016 年,当时还没有实现这个快捷方式。
【解决方案2】:

如果您的 keras 包装器模型位于 scikit 管道中,您可以单独保存管道中的步骤。

import joblib
from sklearn.pipeline import Pipeline
from tensorflow import keras

# pass the create_cnn_model function into wrapper
cnn_model = keras.wrappers.scikit_learn.KerasClassifier(build_fn=create_cnn_model)

# create pipeline
cnn_model_pipeline_estimator = Pipeline([
    ('preprocessing_pipeline', pipeline_estimator),
    ('clf', cnn_model)
])

# train model
final_model = cnn_model_pipeline_estimator.fit(
X, y, clf__batch_size=32, clf__epochs=15)

# collect the preprocessing pipeline & model seperately
pipeline_estimator = final_model.named_steps['preprocessing_pipeline']
clf = final_model.named_steps['clf']

# store pipeline and model seperately
joblib.dump(pipeline_estimator, open('path/to/pipeline.pkl', 'wb'))
clf.model.save('path/to/model.h5')

# load pipeline and model
pipeline_estimator = joblib.load('path/to/pipeline.pxl')
model = keras.models.load_model('path/to/model.h5')

new_example = [[...]]

# transform new data with pipeline & use model for prediction
transformed_data = pipeline_estimator.transform(new_example)
prediction = model.predict(transformed_data)

【讨论】:

  • 感谢您分享这个答案!你能解释一下为什么需要拆分并且不能将整个管道保存为一个 pkl 文件吗?
  • 来自this keras issue #4274 我觉得整个管道可以保存为一个pkl文件
  • BTW - 使用此解决方案model.predict 返回pipeline.predict_proba 的值,因此您需要使用predict_classes
【解决方案3】:

另一个很好的选择是在你fit你的模型时使用callbacks。特别是ModelCheckpoint 回调,像这样:

from keras.callbacks import ModelCheckpoint
#Create instance of ModelCheckpoint
chk = ModelCheckpoint("myModel.h5", monitor='val_loss', save_best_only=False)
#add that callback to the list of callbacks to pass
callbacks_list = [chk]
#create your model
model_tt = KerasClassifier(build_fn=create_model, nb_epoch=150, batch_size=10)
#fit your model with your data. Pass the callback(s) here
model_tt.fit(X_train,y_train, callbacks=callbacks_list)

这会将您的训练每个时期保存到myModel.h5 文件中。这提供了很大的好处,因为您可以在需要时停止训练(例如当您看到它开始过度拟合时),并且仍然保留之前的训练。

请注意,这会将结构和权重保存在同一个 hdf5 文件中(如 Zach 所示),因此您可以使用 keras.models.load_model 加载模型。

如果您只想单独保存权重,则可以在实例化 ModelCheckpoint 时使用 save_weights_only=True 参数,使您能够按照 Gaarv 的说明加载模型。提取自docs

save_weights_only:如果为 True,则仅保存模型的权重 (model.save_weights(filepath)),否则保存完整模型 (model.save(filepath))。

【讨论】:

    【解决方案4】:

    只是添加到 gaarv 的答案 - 如果您不需要分离模型结构 (model.to_json()) 和权重 (model.save_weights()),您可以使用以下方法之一:

    • 使用内置的 keras.models.save_model 和 'keras.models.load_model` 将所有内容一起存储在 hdf5 文件中。
    • 使用 pickle 将 Model 对象(或任何包含对它的引用的类)序列化为文件/网络/任何东西..
      不幸的是,Keras 默认不支持 pickle。您可以使用 我的补丁解决方案添加了这个缺失的功能。工作代码是 在这里:http://zachmoshe.com/2017/04/03/pickling-keras-models.html

    【讨论】:

    • 感谢您提供此补丁。但是使用KerasClassifier,我现在在尝试加载 pkl 时遇到不同的错误:AttributeError: Can't get attribute 'create_model' on &lt;module '__main__'&gt;——似乎build_fn 被引用但未存储在pickle 中。有什么建议吗?
    【解决方案5】:

    编辑1:关于保存模型的原始答案

    使用 HDF5:

    # saving model
    json_model = model_tt.model.to_json()
    open('model_architecture.json', 'w').write(json_model)
    # saving weights
    model_tt.model.save_weights('model_weights.h5', overwrite=True)
    
    
    # loading model
    from keras.models import model_from_json
    
    model = model_from_json(open('model_architecture.json').read())
    model.load_weights('model_weights.h5')
    
    # dont forget to compile your model
    model.compile(loss='binary_crossentropy', optimizer='adam')
    

    编辑 2 :带有 iris 数据集的完整代码示例

    # Train model and make predictions
    import numpy
    import pandas
    from keras.models import Sequential, model_from_json
    from keras.layers import Dense
    from keras.utils import np_utils
    from sklearn import datasets
    from sklearn import preprocessing
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import LabelEncoder
    
    # fix random seed for reproducibility
    seed = 7
    numpy.random.seed(seed)
    
    # load dataset
    iris = datasets.load_iris()
    X, Y, labels = iris.data, iris.target, iris.target_names
    X = preprocessing.scale(X)
    
    # encode class values as integers
    encoder = LabelEncoder()
    encoder.fit(Y)
    encoded_Y = encoder.transform(Y)
    
    # convert integers to dummy variables (i.e. one hot encoded)
    y = np_utils.to_categorical(encoded_Y)
    
    def build_model():
        # create model
        model = Sequential()
        model.add(Dense(4, input_dim=4, init='normal', activation='relu'))
        model.add(Dense(3, init='normal', activation='sigmoid'))
        model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
        return model
    
    def save_model(model):
        # saving model
        json_model = model.to_json()
        open('model_architecture.json', 'w').write(json_model)
        # saving weights
        model.save_weights('model_weights.h5', overwrite=True)
    
    def load_model():
        # loading model
        model = model_from_json(open('model_architecture.json').read())
        model.load_weights('model_weights.h5')
        model.compile(loss='categorical_crossentropy', optimizer='adam')
        return model
    
    
    X_train, X_test, Y_train, Y_test = train_test_split(X, y, test_size=0.3, random_state=seed)
    
    # build
    model = build_model()
    model.fit(X_train, Y_train, nb_epoch=200, batch_size=5, verbose=0)
    
    # save
    save_model(model)
    
    # load
    model = load_model()
    
    # predictions
    predictions = model.predict_classes(X_test, verbose=0)
    print(predictions)
    # reverse encoding
    for pred in predictions:
        print(labels[pred])
    

    请注意,我只使用了 Keras,而不是包装器。它只会在简单的事情中增加一些复杂性。此外,代码是自愿的,不考虑因素,因此您可以了解整个情况。

    另外,您说过要输出 1 或 0。在此数据集中这是不可能的,因为您有 3 个输出尺寸和类别(Iris-setosa、Iris-versicolor、Iris-virginica)。如果您只有 2 个类,那么使用 sigmoid 输出函数,您的输出 dim 和类将为 0 或 1。

    【讨论】:

    • 对于登陆这里的任何其他人......(已接受)答案中的帮助函数实际上并没有帮助。具有这些辅助函数的完整代码示例有效,因为在该代码示例中,Keras 模型从未包装为 sklearn KerasClassifier(如问题引用)。保存 sklearn KerasClassifier 对象的真正答案在此 github 对同一问题的回答中给出:github.com/fchollet/keras/issues/4274 这是对 KerasClassifier 对象的 .model 属性使用内置 Keras 保存方法:your_model_name.model.save( '保存的文件名.h5')
    • 您好,是的,它没有解决有关 KerasClassifier 的问题,实际上我在评论中的代码下方提到了这一点。附带说明一下,包装器应该有一些东西放在桌子上,我在这里看不到。 Keras 已经足够简单/紧凑,不需要任何包装。
    猜你喜欢
    • 2017-12-17
    • 1970-01-01
    • 2017-07-13
    • 2021-01-19
    • 2022-08-23
    • 2017-03-16
    • 1970-01-01
    • 2019-10-03
    相关资源
    最近更新 更多