I'm starting to dive into deploying a predictive model to a web app using Flask, and unfortunately getting stuck at the starting gate.
What I did:
I pickled my model in my model.py program:
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')
then I created an api.py file in the same directory as my demo_model.pkl, per this tutorial (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)
I also made a templates/index.html file in the same directory with this info:
<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>
running:
>> python api.py
gives me an error with the 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'
Why is the main module of the program getting involved with my NeuralNetwork model? I'm very confused at the moment... any advice would be appreciated.
UPDATE:
Adding a class definition class NeuralNetwork(object): pass
to my api.py program fixed the bug.
import flask
from flask import Flask, render_template, request
from sklearn.externals import joblib
class NeuralNetwork(object):
pass
app = Flask(__name__)
If anyone would be willing to offer me an explanation of what was going on that would be hugely appreciated!
The specific exception you're getting refers to attributes in __main__
, but that's mostly a red herring. I'm pretty sure the issue actually has to do with how you dumped the instance.
Pickle does not dump the actual code classes and functions, only their names. It includes the name of the module each one was defined in, so it can find them again. If you dump a class defined in a module you're running as a script, it will dump the name __main__
as the module name, since that's what Python uses as the name for the main module (as seen in the if __name__ == "__main__"
boilerplate code).
When you run model.py
as a script and pickle an instance of a class defined in it, that class will be saved as __main__.NeuralNetwork
rather than model.NeuralNetwork
. When you run some other module and try to load the pickle file, Python will look for the class in the __main__
module, since that's where the pickle data tells it to look. This is why you're getting an exception about attributes of __main__
.
To solve this you probably want to change how you're dumping the data. Instead of running model.py
as a script, you should probably run some other module and have it do import model
, so you get the module under it's normal name. (I suppose you could have model.py
import itself in an if __name__ == "__main__"
block, but that's super ugly and awkward). You probably also need to avoid recreating and dumping the instance unconditionally when the model
is imported, since that needs to happen when you load the pickle file (and I assume the whole point of the pickle is to avoid recreating the instance from scratch).
So remove the dumping logic from the bottom of model.py
, and add a new file like this:
# 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')
When you dump the NeuralNetwork
using this script, it will correctly identify model
as the module the class was defined in, and so the loading code will be able to import that module and make an instance of the class correctly.
Your current "fix" for the issue (defining an empty NeuralNetwork
class in the __main__
module when you are loading the object) is probably a bad solution. The instance you get from loading the pickle file will be an instance of the new class, not the original one. It will be loaded with the attributes of the old instance, but it won't have any methods or other class variables set on it (which isn't an issue with the class you've shown, but probably will be for any kind of object that's more complicated).