我开始尝试将预测模型部署到使用Flask的Web应用程序中,但不幸的是在起步阶段就遇到了问题。
我所做的:
我在我的model.py程序中对模型进行了pickle处理:
import numpy as npfrom sklearn.externals import joblibclass 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 flaskfrom flask import Flask, render_template, requestfrom sklearn.externals import joblibapp = 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'
为什么程序的主模块会与我的NeuralNetwork模型有关联?我现在很困惑…任何建议都将不胜感激。
更新:
在我的api.py程序中添加一个类定义class NeuralNetwork(object): pass
修复了这个错误。
import flaskfrom flask import Flask, render_template, requestfrom sklearn.externals import joblibclass NeuralNetwork(object): passapp = Flask(__name__)
如果有人愿意解释一下这是怎么回事,我将非常感激!
回答:
你遇到的特定异常提到了__main__
中的属性,但这基本上是个误导。我相当确定问题实际上与你如何保存实例有关。
Pickle不会保存实际的代码类和函数,只保存它们的名称。它包括每个定义所在的模块名称,以便它可以再次找到它们。如果你保存了一个在作为脚本运行的模块中定义的类,它会将模块名称保存为__main__
,因为这是Python用于主模块的名称(如if __name__ == "__main__"
样板代码中所见)。
当你作为脚本运行model.py
并pickle一个在其中定义的类的实例时,该类将被保存为__main__.NeuralNetwork
而不是model.NeuralNetwork
。当你运行其他模块并尝试加载pickle文件时,Python会查找__main__
模块中的类,因为pickle数据告诉它要查找那里。这就是你得到关于__main__
属性的异常的原因。
为了解决这个问题,你可能需要改变保存数据的方式。不要作为脚本运行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 NeuralNetworkfrom sklearn.externals import joblibfrom model import NeuralNetworkif __name__ == "__main__": model = NeuralNetwork(input_dim=12, units=64) joblib.dump(model, 'demo_model.pkl')
当你使用这个脚本保存NeuralNetwork
时,它会正确地识别model
为定义类的模块,因此加载代码将能够导入该模块并正确地创建类的实例。
你当前解决问题的方法(在加载对象时在__main__
模块中定义一个空的NeuralNetwork
类)可能是一个不好的解决方案。从pickle文件加载的实例将是新类的实例,而不是原始的实例。它将加载旧实例的属性,但不会有任何方法或其他类变量设置(对于你展示的类没有问题,但对于任何更复杂的对象可能就会有问题)。