*This article is based on PaddlePaddle 0.11.0 and Python 2.7

Foreword


If readers have used some image recognition interfaces from Baidu, such as Baidu’s Fine-grained Image Recognition interface, they should understand the process, ignoring other security considerations. The general process of this interface is: we upload images to Baidu’s server, the server converts these images into vector data, and finally this data is passed to a deep learning prediction interface (e.g., PaddlePaddle’s prediction interface) to obtain the prediction result, which is then returned to the client. This is just a simple process; the actual complexity is far more involved, but we only need to understand these basics to build our own image recognition interface.

Environment


  1. System: 64-bit Ubuntu 16.04
  2. Development language: Python 2.7
  3. Web framework: Flask
  4. Prediction interface: Image recognition

Familiarity with Flask


Install Flask

Installing Flask is straightforward with a single command:

pip install flask

We also use flask_cors for cross-origin resource sharing, so we need to install this library as well:

pip install flask_cors

These are the main libraries. If any other libraries are missing, use the pip command with * as a placeholder for the missing library:

pip install *

Test the Flask Framework

Let’s write a simple program to test the installed framework. Using @app.route('/') specifies the access path:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Welcome to PaddlePaddle'

if __name__ == '__main__':
    app.run()

Then enter the following address in your browser:

http://127.0.0.1:5000

The browser will return the string we wrote earlier:

Welcome to PaddlePaddle

File Upload

Let’s write a program to handle file uploads. This is slightly more complex, so we need to pay attention to the following:
- secure_filename ensures we can correctly retrieve the uploaded file’s name.
- flask_cors enables cross-origin access.
- methods=['POST'] restricts this route to POST requests only.
- f = request.files['img'] reads the file with the form name img.
- f.save(img_path) saves the file to the specified path.

from werkzeug.utils import secure_filename
from flask import Flask, request
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route('/upload', methods=['POST'])
def upload_file():
    f = request.files['img']
    img_path = './data/' + secure_filename(f.filename)
    print img_path
    f.save(img_path)
    return 'success'

We also create an HTML webpage index.html to test the interface:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Image Prediction</title>
</head>
<body>
<form action="http://127.0.0.1:5000/upload" enctype="multipart/form-data" method="post">
    Select an image to predict: <input type="file" name="img"><br>
    <input type="submit" value="Submit">
</form>
</body>
</html>

Open this webpage in your browser, select a file, and click “Submit”. If the response is success, the file was uploaded successfully. You can check the saved location to confirm the file exists.

Using PaddlePaddle for Prediction


Obtain the Prediction Model

We use the MNIST handwritten digit recognition example from Chapter 2, as it trains quickly and provides the necessary prediction model. The code is similar to Chapter 2 but adds functionality to generate the inference topology:

# Save the inference topology graph
inference_topology = paddle.topology.Topology(layers=out)
with open("../models/inference_topology.pkl", 'wb') as f:
    inference_topology.serialize_for_inference(f)

We also remove the testing part to speed up training:

result = trainer.test(reader=paddle.batch(paddle.dataset.mnist.test(), batch_size=128))
print "\nTest with Pass %d, Cost %f, %s\n" % (event.pass_id, result.cost, result.metrics)
lists.append((event.pass_id, result.cost, result.metrics['classification_error_evaluator']))

This will generate two files:
1. param.tar (model parameters file)
2. inference_topology.pkl (prediction topology file)

Deploy PaddlePaddle to the Server

First, we create a queue to handle PaddlePaddle predictions:

app = Flask(__name__)
CORS(app)
# Create the main queue
sendQ = Queue()

Next, we write the file upload interface for inference:

@app.route('/infer', methods=['POST'])
def infer():
    # Get the uploaded image
    f = request.files['img']
    img_path = './data/' + secure_filename(f.filename)
    print img_path
    # Save the uploaded image
    f.save(img_path)
    # Convert the uploaded image to vector data
    data = []
    data.append((load_image(img_path),))
    # print 'Prediction data:', data

    # Create a sub-queue
    recv_queue = Queue()
    # Send data and sub-queue to the main queue
    sendQ.put((data, recv_queue))
    # Retrieve the prediction result from the sub-queue
    success, resp = recv_queue.get()
    if success:
        # Return prediction result if successful
        return successResp(resp)
    else:
        # Return error message if failed
        return errorResp(resp)

The code for uploading and saving files was explained earlier. The key part is converting the image to vector data:

data = []
data.append((load_image(img_path),))

The load_image() function remains the same as before:

def load_image(img_path):
    im = Image.open(img_path).convert('L')
    im = im.resize((28, 28), Image.ANTIALIAS)
    im = np.array(im).astype(np.float32).flatten()
    im = im / 255.0
    return im

Now, we use a thread for PaddlePaddle inference:
- PaddlePaddle initialization and model loading should be done once, outside the loop.
- In the loop, we fetch image data and sub-queues from the main queue.
- Perform prediction with the image data and return results via the sub-queue.

# Create a PaddlePaddle prediction thread
def worker():
    # Initialize PaddlePaddle
    paddle.init(use_gpu=False, trainer_count=2)

    # Load model parameters and prediction topology to create a predictor
    with open('../models/param.tar', 'r') as param_f:
        params = paddle.parameters.Parameters.from_tar(param_f)

    # Get the classifier
    out = convolutional_neural_network()

    while True:
        # Fetch data and sub-queue from the main queue
        data, recv_queue = sendQ.get()
        try:
            # Perform prediction
            result = paddle.infer(output_layer=out,
                                  parameters=params,
                                  input=data)

            # Process the prediction result
            lab = np.argsort(-result)
            print lab
            # Return the predicted label and probability
            result_str = '{"result":%d,"possibility":%f}' % (lab[0][0], result[0][(lab[0][0])])
            print result_str
            recv_queue.put((True, result_str))
        except:
            # Send error message via sub-queue
            trace = traceback.format_exc()
            print trace
            recv_queue.put((False, trace))
            continue

Back in the infer() function, we retrieve the prediction result from the sub-queue and return it:

# Get the result from the sub-queue
success, resp = recv_queue.get()
if success:
    # Return prediction result if successful
    return successResp(resp)
else:
    # Return error message if failed
    return errorResp(resp)

Finally, define functions to format the response data (JSON format):

# Error response
def errorResp(msg):
    return jsonify(code=-1, message=msg)

# Success response
def successResp(data):
    return jsonify(code=0, message="success", data=data)

Start the prediction thread and the server:

if __name__ == '__main__':
    t = threading.Thread(target=worker)
    t.daemon = True
    t.start()
    # Change the port to 80 for accessibility
    app.run(host='0.0.0.0', port=80, threaded=True)

Open the index.html file in your browser, update the form’s action to http://127.0.0.1/infer, select an image, and click “Submit”. The prediction result will be returned as JSON:

{
  "code": 0, 
  "data": "{\"result\":3,\"possibility\":1.000000}", 
  "message": "success"
}


Previous chapter: 《My PaddlePaddle Learning Journey》Note Twelve——Using VisualDL Visualization Tool
Next chapter: 《My PaddlePaddle Learning Journey》Note Fourteen——Migrate PaddlePaddle to Android Devices


Project Code


GitHub address: https://github.com/yeyupiaoling/LearnPaddle

References


  1. http://paddlepaddle.org/
  2. http://blog.csdn.net/u011054333/article/details/70151857
Xiaoye