87·工程化与部署进阶

Flask 构建模型推理 API

deploymentflask

第 87 课 - Flask 构建模型推理 API

所属模块: 工程化与部署
难度: Intermediate
标签: deployment, flask
上一课: 模型持久化:Pickle、Joblib 与 ONNX
下一课: FastAPI 高性能模型服务


1. 学习目标

完成本课学习后,你将能够:

  1. 理解 Flask 框架在机器学习模型部署中的核心作用。
  2. 掌握使用 Flask 创建 RESTful API 以提供模型推理服务的完整流程。
  3. 学会接收 JSON 格式的输入数据,调用已加载的模型进行预测,并返回格式化的结果。
  4. 了解在生产环境中部署此类服务的基本注意事项。

2. 核心概念

Flask:轻量级的Web服务窗口

Flask 是一个用 Python 编写的轻量级 Web 应用框架。我们可以把它想象成一个“服务窗口”。客户端(比如一个前端网页或另一个后端程序)通过发送请求到这个窗口,窗口后面的服务程序(我们的模型代码)处理请求,然后将结果(比如预测值)送回给客户端。

模型推理 API:模型的“接客”接口

在上一课,我们学习了如何将训练好的模型保存到文件中。现在,我们需要让这个“大脑”能够工作。构建一个 API(应用程序接口)就是为这个大脑创建一个标准化的“接客”接口。其他程序只需按照约定(例如,发送一个包含房屋面积的JSON数据),就能通过这个接口获得预测结果(例如,房屋价格)。

核心交互流程

  1. 启动服务:加载训练好的模型,并启动一个 Web 服务器,监听特定端口。
  2. 接收请求:客户端发送一个 HTTP POST 请求到我们定义的 URL(例如 /predict),请求体中携带待预测的数据(通常是 JSON 格式)。
  3. 处理数据:服务器解析 JSON 数据,将其转换为模型所需的格式(如 NumPy 数组或 Pandas DataFrame)。
  4. 模型推理:调用加载的模型进行预测。
  5. 返回结果:将预测结果包装成 JSON 格式返回给客户端。

3. 代码示例

假设我们有一个训练好的房价预测线性回归模型,并已使用 joblib 保存为 house_price_model.joblib

第一步:准备模型(如果需要,可先运行此代码生成一个示例模型)

# train_and_save_model.py
from sklearn.linear_model import LinearRegression
import joblib
import numpy as np

# 模拟训练数据:面积 -> 价格
X_train = np.array([[50], [80], [100], [120], [150]])
y_train = np.array([150, 240, 300, 360, 450])

# 训练一个简单的线性回归模型
model = LinearRegression()
model.fit(X_train, y_train)

# 保存模型
joblib.dump(model, 'house_price_model.joblib')
print(“模型已保存!”)

第二步:构建 Flask API 服务

# flask_model_serving.py
from flask import Flask, request, jsonify
import joblib
import numpy as np

# 1. 创建 Flask 应用实例
app = Flask(__name__)

# 2. 加载模型
# 使用一个全局变量存储模型,避免每次请求都加载
model = joblib.load('house_price_model.joblib')
print(“模型加载完成!”)

# 3. 定义健康检查接口(可选,但推荐)
@app.route('/health', methods=['GET'])
def health_check():
    """用于监控服务是否存活"""
    return jsonify({'status': 'healthy'})

# 4. 定义预测接口
@app.route('/predict', methods=['POST'])
def predict():
    """接收面积,返回预测价格"""
    # 获取请求的 JSON 数据
    data = request.get_json(force=True)
    
    # 输入验证(非常重要!)
    if 'area' not in data:
        return jsonify({'error': '缺少面积参数 area'}), 400
    
    try:
        area = float(data['area'])
    except (ValueError, TypeError):
        return jsonify({'error': '面积必须是数字'}), 400
    
    # 将输入数据转换为模型期望的格式(2D 数组)
    input_data = np.array([[area]])
    
    # 进行预测
    predicted_price = model.predict(input_data)[0]
    
    # 构造并返回响应
    response = {
        'area': area,
        'predicted_price': round(float(predicted_price), 2)  # 保留两位小数
    }
    return jsonify(response)

# 5. 启动应用
if __name__ == '__main__':
    # debug=True 在开发时使用,便于调试和热重载
    # 生产环境应关闭 debug,并使用生产级 WSGI 服务器(如 Gunicorn)
    app.run(host='0.0.0.0', port=5000, debug=True)

如何使用此 API:

  1. 先运行 train_and_save_model.py 生成模型文件。
  2. 运行 flask_model_serving.py 启动服务器。
  3. 使用 curl 或任何 HTTP 客户端发送请求测试:
    curl -X POST http://localhost:5000/predict \
         -H "Content-Type: application/json" \
         -d '{"area": 110}'
    
    预期输出:
    {
      "area": 110.0,
      "predicted_price": 330.0
    }
    
  4. 测试健康检查:
    curl http://localhost:5000/health
    

4. 实践练习

练习 1:基础扩展

在现有 API 的基础上,增加一个 /model_info 接点(GET 方法),返回模型的类型(LinearRegression)和它被加载的时间。

要求:

  • 使用 Python 的 datetime 模块记录模型加载时间。
  • 返回的 JSON 应包含 model_typeloaded_at 字段。

练习 2:增强输入验证

修改 /predict 接口,使其能够接收一个包含多个房屋面积的列表 areas,并返回对应的预测价格列表。

要求:

  • 输入示例:{"areas": [100, 150, 200]}
  • 如果 areas 不是列表,或者列表中包含非数字,返回明确的错误信息。
  • 预期输出: {"areas": [100, 150, 200], "predicted_prices": [300.0, 450.0, 600.0]}

练习 3:数据预处理集成

假设你的模型需要两个特征:area(面积)和 rooms(房间数)。修改 API,使其能接收这两个字段,并将它们合并后输入模型。模型需要你重新训练(可以使用随机数据)。

要求:

  • 创建一个新的模型,使用两个特征进行训练。
  • 修改 /predict 接口,接收 arearooms
  • 验证两个字段都存在且为数字。
  • 预期输入:{"area": 110, "rooms": 3}
  • 预期输出: {"area": 110.0, "rooms": 3, "predicted_price": [一个合理数值]}

5. 常见错误

  1. 忽略输入验证:直接信任客户端发送的数据是危险的。务必检查必需字段是否存在、数据类型是否正确(如是否为数字),并为可能的转换错误(如将字符串转为浮点数)提供友好的错误提示。
  2. 模型输入形状不匹配scikit-learn 模型的 predict 方法通常需要一个二维数组(即使只有一个样本)。常见错误是传入一维数组,导致 ValueError。解决方法是使用 np.array([[data]]) 进行包装。
  3. 在请求处理函数内加载模型:如果将 model = joblib.load(...) 放在 predict() 函数内部,会导致每个请求都重新加载模型文件,效率极低。应将其放在全局作用域或应用初始化阶段。
  4. 生产环境使用 Flask 内置服务器:Flask 自带的 app.run() 服务器仅适用于开发和测试。它单线程、无安全加固,无法应对生产环境的并发压力。应使用 GunicornuWSGI 等专业的 WSGI 服务器来部署。
  5. CORS(跨域资源共享)问题:当前端网页(如运行在 localhost:3000)直接调用运行在 localhost:5000 的 Flask API 时,浏览器会因同源策略阻止请求。在开发时,可安装 flask-cors 扩展轻松解决。

6. 小结

  • Flask 的角色:Flask 是构建机器学习模型推理 API 的轻量级、灵活的工具,它充当了客户端与模型之间的桥梁。
  • API 设计核心:一个清晰的 RESTful API 设计应包含明确的端点(如 /predict)、合适的 HTTP 方法(如 POST)、结构化的请求/响应(通常使用 JSON),以及充分的错误处理。
  • 关键步骤:构建服务的流程是 加载模型 -> 定义路由 -> 处理请求(验证、预处理、推理、格式化) -> 返回响应
  • 生产考量:本课使用的是开发服务器,真正的生产部署需要考虑使用生产级 WSGI 服务器、添加认证、监控日志、容器化(Docker)以及设置合理的超时和并发策略。

在下一课中,我们将学习如何使用 FastAPI,一个现代的、高性能的 Web 框架来构建同样的服务,它在类型提示、自动生成文档和异步支持方面有显著优势。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「FastAPI 高性能模型服务」 以巩固所学知识。