joblib.load __main__ 属性错误

joblib.load __main__ AttributeError

我开始深入研究使用 Flask 将预测模型部署到 Web 应用程序,不幸的是,我被困在起跑线上。

我做了什么:

我在我的 model.py 程序中腌制了我的模型:

import numpy as np
from sklearn.externals import joblib

class NeuralNetwork():
    """
    Two (hidden) layer neural network model. 
    First and second layer contain the same number of hidden units
    """
    def __init__(self, input_dim, units, std=0.0001):
        self.params = {}
        self.input_dim = input_dim

        self.params['W1'] = np.random.rand(self.input_dim, units)
        self.params['W1'] *= std
        self.params['b1'] = np.zeros((units))

        self.params['W2'] = np.random.rand(units, units)
        self.params['W2'] *= std * 10  # Compensate for vanishing gradients
        self.params['b2'] = np.zeros((units))

        self.params['W3'] = np.random.rand(units, 1)
        self.params['b3'] = np.zeros((1,))

model = NeuralNetwork(input_dim=12, units=64)

#####THIS RIGHT HERE ##############
joblib.dump(model, 'demo_model.pkl')

然后我根据本教程在与 demo_model.pkl 相同的目录中创建了一个 api.py 文件(https://blog.hyperiondev.com/index.php/2018/02/01/deploy-machine-learning-models-flask-api/):

import flask
from flask import Flask, render_template, request
from sklearn.externals import joblib

app = Flask(__name__)


@app.route("/")
@app.route("/index")
def index():
    return flask.render_template('index.html')


# create endpoint for the predictions (HTTP POST requests)
@app.route('/predict', methods=['POST'])
def make_prediction():
    if request.method == 'POST':
        return render_template('index.html', label='3')


if __name__ == '__main__':
    # LOAD MODEL WHEN APP RUNS ####
    model = joblib.load('demo_model.pkl')
    app.run(host='0.0.0.0', port=8000, debug=True)

我还在同一目录中制作了一个 templates/index.html 文件,其中包含以下信息:

<html>
    <head>
        <title>NN Model as Flask API</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <h1>Boston Housing Price Predictor</h1>
        <form action="/predict" method="post" enctype="multipart/form-data">
            <input type="file" name="image" value="Upload">
            <input type="submit" value="Predict"> {% if label %} {{ label }} {% endif %}
        </form>
    </body>

</html>

运行:

>> python api.py

给我一个 pickler 错误:

Traceback (most recent call last):
  File "api.py", line 22, in <module>
    model = joblib.load('model.pkl')
  File "C:\Users\joshu\Anaconda3\lib\site-packages\sklearn\externals\joblib\numpy_pickle.py", line 578, in load
    obj = _unpickle(fobj, filename, mmap_mode)
  File "C:\Users\joshu\Anaconda3\lib\site-packages\sklearn\externals\joblib\numpy_pickle.py", line 508, in _unpickle
    obj = unpickler.load()
  File "C:\Users\joshu\Anaconda3\lib\pickle.py", line 1043, in load
    dispatch[key[0]](self)
  File "C:\Users\joshu\Anaconda3\lib\pickle.py", line 1342, in load_global
    klass = self.find_class(module, name)
  File "C:\Users\joshu\Anaconda3\lib\pickle.py", line 1396, in find_class
    return getattr(sys.modules[module], name)
AttributeError: module '__main__' has no attribute 'NeuralNetwork'

为什么程序的主模块会涉及到我的神经网络模型?我现在很困惑...任何建议将不胜感激。

更新:

向我的 api.py 程序添加 class 定义 class NeuralNetwork(object): pass 修复了该错误。

import flask
from flask import Flask, render_template, request
from sklearn.externals import joblib


class NeuralNetwork(object):
    pass


app = Flask(__name__)

如果有人愿意向我解释发生了什么,将不胜感激!

您得到的特定异常是指 __main__ 中的属性,但这主要是转移注意力。我很确定这个问题实际上与您转储实例的方式有关。

Pickle 不会转储实际代码 classes 和函数,只会转储它们的名称。它包括每个定义所在的模块的名称,因此它可以再次找到它们。如果转储模块中定义的 class 作为脚本,它会将名称 __main__ 转储为模块名称,因为这就是 Python 使用的名称作为主模块的名称(如 if __name__ == "__main__" 样板代码所示)。

当您 运行 model.py 作为脚本并 pickle 一个在其中定义的 class 的实例时,class 将被保存为 __main__.NeuralNetwork而不是 model.NeuralNetwork。当您 运行 一些其他模块并尝试加载 pickle 文件时,Python 将在 __main__ 模块中查找 class,因为 pickle 数据告诉它看。这就是为什么您会收到有关 __main__.

属性的异常的原因

要解决这个问题,您可能需要更改转储数据的方式。而不是 运行ning model.py 作为脚本,您可能应该 运行 一些其他模块并让它执行 import model,这样您就可以在它的正常名称下获得模块。 (我想你可以让 model.py 将自身导入到 if __name__ == "__main__" 块中,但这非常丑陋和笨拙)。您可能还需要避免在导入 model 时无条件地重新创建和转储实例,因为这需要在您加载 pickle 文件时发生(我假设 pickle 的全部要点是避免从重新创建实例从头开始)。

所以删除model.py底部的转储逻辑,并添加一个新文件,如下所示:

# new script, dump_model.py, does the creation and dumping of the NeuralNetwork

from sklearn.externals import joblib

from model import NeuralNetwork

if __name__ == "__main__":
    model = NeuralNetwork(input_dim=12, units=64)
    joblib.dump(model, 'demo_model.pkl')

当您使用此脚本转储 NeuralNetwork 时,它会正确地将 model 识别为定义 class 的模块,因此加载代码将能够导入该模块并正确创建 class 的实例。

您当前的 "fix" 问题(加载对象时在 __main__ 模块中定义一个空的 NeuralNetwork class)可能是一个糟糕的解决方案。您从加载 pickle 文件中获得的实例将是新 class 的实例,而不是原始实例。它将加载旧实例的属性,但不会在其上设置任何方法或其他 class 变量(这不是您显示的 class 的问题,但可能适用于更复杂的任何类型的对象)。

如果您使用 Keras 库来构建您的神经网络,那么 pickle 将不起作用。 pickle 仅适用于使用 scikit 库构建的模型。使用 json .

保存你的神经网络模型

Keras 提供使用 JSON 格式和 to_json() 函数描述任何模型的能力。这可以保存到文件中,稍后通过 model_from_json() 函数加载,该函数将从 JSON 规范创建一个新模型。

# serialize model to JSON
    model_json = model.to_json()
    with open(“model.json”, “w”) as json_file:
    json_file.write(model_json)

# serialize weights to HDF5
    model.save_weights(“model.h5”)
    print(“Saved model to disk”)

# later…

# load json and create model
    json_file = open(‘model.json’, ‘r’)
    loaded_model_json = json_file.read()
    json_file.close()
    loaded_model = model_from_json(loaded_model_json)

# load weights into new model
    loaded_model.load_weights(“model.h5”)
    print(“Loaded model from disk”)