Python - Scipy 优化并 Return Flask 中的值 - Restful

Python - Scipy Optimize And Return Value In Flask-Restful

我正在尝试使用 Flask 构建一个 REST API,其中 returns 值来自 Scipy 的 minimize 函数。我能够得到结果,但我想在 API 调用中公开它,并且此代码出错:

import scipy.stats as sp
from scipy.optimize import minimize
from flask import Flask, g, Response
from flask_restful import Resource, Api, reqparse
import numpy as np

app = Flask(__name__)

api = Api(app)


class Minimize(Resource):
    result = None

    def _calculate_probability(self, spread, std_dev):
        return sp.norm.sf(0.5, spread, scale=std_dev)

    def _calculate_mse(self, std_dev):
        spread_inputs = np.array(self.spreads)
        model_probabilities = self._calculate_probability(spread_inputs, std_dev)
        mse = np.sum((model_probabilities - self.expected_probabilities)**2) / len(spread_inputs)
        return mse

    def __init__(self, expected_probabilities, spreads, std_dev_guess):
        self.std_dev_guess = std_dev_guess
        self.spreads = spreads
        self.expected_probabilities = expected_probabilities

    def solve(self):
        self.result = minimize(self._calculate_mse, self.std_dev_guess, method='BFGS')

    def get(self):
        return {'data': self.result}, 200

api.add_resource(Minimize, '/minimize')

我可以将答案打印到控制台:

spreads = [10.5, 9.5, 10, 8.5]
expected_probabilities = [0.8091, 0.7785, 0.7708, 0.7692]
minimizer = Minimize(expected_probabilities, spreads, 12.0)
minimizer.solve()
print(minimizer.get())

我明白了:

probability-calculator_1  | ({'data':       fun: 0.00018173060393236452
probability-calculator_1  |  hess_inv: array([[1381.37379663]])
probability-calculator_1  |       jac: array([-1.56055103e-06])
probability-calculator_1  |   message: 'Optimization terminated successfully.'
probability-calculator_1  |      nfev: 24
probability-calculator_1  |       nit: 3
probability-calculator_1  |      njev: 8
probability-calculator_1  |    status: 0
probability-calculator_1  |   success: True
probability-calculator_1  |         x: array([11.70822653])}, 200)

但是,当我向 localhost:5000/minimize 发出 GET 请求时,这是错误响应:

TypeError: __init__() missing 3 required positional arguments: 'expected_probabilities', 'spreads', and
        'std_dev_guess'

如何定义 API 调用以便它 returns 打印答案?

编辑:所以我添加了另一个 class 来尝试获得对 POST 请求的响应。

class MinimisedError(Resource):

    def post(self):

        parser = reqparse.RequestParser()
        parser.add_argument('spread_inputs', action='append', required=True)
        parser.add_argument('expected_probabilities',
                            action='append', required=True)
        parser.add_argument('std_dev', required=True)

        args = parser.parse_args()

        minimizer = Minimize(args.spread_inputs,
                             args.expected_probabilities, float(args.std_dev))
        minimizer.solve()

        return {minimizer.get()}, 200

api.add_resource(MinimisedError, '/minimize')

当我尝试使用正文 POST 时

{
    "expected_probabilities":[0.8091, 0.7785, 0.7708, 0.7692],
    "spread_inputs":[10.5, 9.5, 10, 8.5],
    "std_dev":12.0
}

我收到这样的回复:

numpy.core._exceptions.UFuncTypeError: ufunc 'subtract' did not contain a loop with signature matching types (dtype('<U32'), dtype('<U32')) -> dtype('<U32')

TL;DR

下面是一个可以解决您的问题的最小的完整可验证示例:

from http import HTTPStatus

import numpy as np
from scipy import stats, optimize

from flask import Flask
from flask_restful import Resource, Api, reqparse

app = Flask(__name__)
api = Api(app)


class OptimizeStdDev(Resource):

    @staticmethod
    def solve(spread, expected, stddev):
        """Solve a specific problem (staticmethod are stateless)"""
        spread = np.array(spread)
        expected = np.array(expected)
        def mse(s):
            estimated = stats.norm.sf(0.5, spread, scale=s)
            mse = np.sum(np.power((estimated - expected), 2))/spread.size
            return mse
        optsol = optimize.minimize(mse, stddev, method='BFGS')
        return optsol
        
    def post(self):
        """Bind optimizer to POST endpoint"""
        parser = reqparse.RequestParser()
        parser.add_argument('spread', action='append', type=float, required=True)
        parser.add_argument('expected', action='append', type=float, required=True)
        parser.add_argument('stddev', type=float, required=True)
        args = parser.parse_args()
        opt = OptimizeStdDev.solve(**args)
        # Convert OptimizeResult as a JSON serializable object:
        res = {k: v.tolist() if isinstance(v, np.ndarray) else v for k, v in opt.items()}
        return res, HTTPStatus.OK
    
api.add_resource(OptimizeStdDev, '/minimize')

def main():
    app.run(debug=True)

if __name__ == "__main__":
    main()

验证

让我们检查一下这个 MCVE 是否真的解决了您的问题:

import requests

data = {
    "expected": [0.8091, 0.7785, 0.7708, 0.7692],
    "spread": [10.5, 9.5, 10, 8.5],
    "stddev": 12.0
}
rep = requests.post("http://127.0.0.1:5000/minimize", json=data)
rep.json()

Returns 以下 JSON 对象:

{
    "fun": 0.00018173060393236452,
    "jac": [-1.5605510270688683e-06],
    "hess_inv": [[1381.3737966283536]],
    "nfev": 24,
    "njev": 8,
    "status": 0,
    "success": True,
    "message": "Optimization terminated successfully.",
    "x": [11.708226529461706],
    "nit": 3
}

这符合您的预期输出。

出了什么问题?

初始代码存在多个问题,主要是:

  • 转换问题,当 API 有效负载通过 Flask 发送时。在 reqparse;
  • 中添加了演员表
  • 在应该使用 POST 的时候使用了 GET 方法(在资源返回之前将数据发送到服务器)。
  • A Resource class 来存储用户输入在使用 Flask 时不是一个好的设计。此外,将用户输入存储到 class 中违反了一个主要的 REST 基本原则:无状态。 class 对于任何客户端都必须是无状态的。相反,我们可以使用 @staticmethod 来确保无状态和带有局部变量的嵌套函数(参见 solvemse)。这就是为什么我完全重构了求解器的实现方式;
  • 返回 scipy.optimize.optimize.OptimizeResult 解决方案对象无效,因为它不是 JSON 可序列化的(numpy.ndarray),相反我们可以在返回时将解决方案字段映射到字典资源(参见 post 方法中的 res 一行)。