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
来确保无状态和带有局部变量的嵌套函数(参见 solve
和 mse
)。这就是为什么我完全重构了求解器的实现方式;
- 返回
scipy.optimize.optimize.OptimizeResult
解决方案对象无效,因为它不是 JSON 可序列化的(numpy.ndarray
),相反我们可以在返回时将解决方案字段映射到字典资源(参见 post
方法中的 res
一行)。
我正在尝试使用 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
来确保无状态和带有局部变量的嵌套函数(参见solve
和mse
)。这就是为什么我完全重构了求解器的实现方式; - 返回
scipy.optimize.optimize.OptimizeResult
解决方案对象无效,因为它不是 JSON 可序列化的(numpy.ndarray
),相反我们可以在返回时将解决方案字段映射到字典资源(参见post
方法中的res
一行)。