Python - Оптимизация Scipy и возврат значения в Flask -Restful - PullRequest
0 голосов
/ 14 июля 2020

Я пытаюсь создать REST API, используя Flask, который возвращает значение из функции 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)

Но когда я делаю запрос GET на localhost:5000/minimize, это ответ с ошибкой:

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

Как мне определить API вызов, чтобы он возвращал напечатанный ответ?

РЕДАКТИРОВАТЬ: Итак, я добавил еще один класс, чтобы попытаться получить ответ на запрос 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')

1 Ответ

1 голос
/ 14 июля 2020

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()

Возвращает следующий объект 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;
  • Использование метода GET, когда вместо него следует использовать POST (вы отправляете данные на свой сервер до возврата ресурса).
  • A Resource класс для хранения пользовательского ввода - не лучший дизайн при использовании Flask. Кроме того, хранение пользовательского ввода в классе нарушает основной фундаментальный принцип REST: безгражданство . Класс должен быть без гражданства по отношению к любым клиентам. Вместо этого мы можем использовать @staticmethod, чтобы гарантировать отсутствие состояния и вложенную функцию с локальными переменными (см. solve и mse). Вот почему я полностью реорганизовал реализацию вашего решателя;
  • Возврат объекта решения scipy.optimize.optimize.OptimizeResult недопустим, потому что он не JSON сериализуемый как это (ни numpy.ndarray), вместо этого мы можем сопоставить решение поля в dict при возврате ресурса (см. res однострочный в методе post).
...