Predicción de aprendizaje automático de Python con una API REST de Flask

Publicado: 2022-03-11

Este artículo trata sobre el uso de Python en el contexto de un sistema de aprendizaje automático o inteligencia artificial (AI) para hacer predicciones en tiempo real, con una API REST de Flask. La arquitectura expuesta aquí puede verse como una forma de pasar de la prueba de concepto (PoC) al producto mínimo viable (MVP) para aplicaciones de aprendizaje automático.

Python no es la primera opción en la que uno puede pensar al diseñar una solución en tiempo real. Pero como Tensorflow y Scikit-Learn son algunas de las bibliotecas de aprendizaje automático más utilizadas compatibles con Python, se usan convenientemente en muchos PoC de Jupyter Notebook.

Lo que hace factible esta solución es el hecho de que el entrenamiento lleva mucho tiempo en comparación con la predicción. Si piensa en el entrenamiento como el proceso de ver una película y predecir las respuestas a las preguntas sobre ella, entonces parece bastante eficiente no tener que volver a ver la película después de cada nueva pregunta.

El entrenamiento es una especie de vista comprimida de esa "película" y la predicción es recuperar información de la vista comprimida. Debe ser muy rápido, ya sea que la película sea compleja o larga.

¡Implementemos eso con un ejemplo rápido de [Flask] en Python!

Arquitectura genérica de aprendizaje automático

Comencemos describiendo un flujo de arquitectura de predicción y entrenamiento genérico:

texto alternativo de la imagen

Primero, se crea una tubería de entrenamiento para aprender sobre los datos pasados ​​de acuerdo con una función objetivo.

Esto debería generar dos elementos clave:

  1. Funciones de ingeniería de características : las transformaciones utilizadas en el momento del entrenamiento deben reutilizarse en el momento de la predicción.
  2. Parámetros del modelo : el algoritmo y los hiperparámetros finalmente seleccionados deben guardarse para que puedan reutilizarse en el momento de la predicción.

Tenga en cuenta que la ingeniería de características realizada durante el tiempo de entrenamiento debe guardarse cuidadosamente para que sea aplicable a la predicción. Un problema habitual entre muchos otros que pueden surgir en el camino es el escalado de funciones, que es necesario para muchos algoritmos.

Si la característica X1 se escala del valor 1 a 1000 y se vuelve a escalar al rango [0,1] con una función f(x) = x/max(X1) , ¿qué sucedería si el conjunto de predicción tiene un valor de 2000?

Se deben pensar algunos ajustes cuidadosos con anticipación para que la función de mapeo devuelva resultados consistentes que se calcularán correctamente en el momento de la predicción.

Entrenamiento de aprendizaje automático frente a predicción

Hay una cuestión importante que abordar aquí. ¿Por qué estamos separando el entrenamiento y la predicción para empezar?

Es absolutamente cierto que en el contexto de los ejemplos y cursos de aprendizaje automático, donde todos los datos se conocen de antemano (incluidos los datos que se van a predecir), una forma muy sencilla de construir el predictor es apilar datos de entrenamiento y predicción (generalmente llamados un equipo de prueba).

Luego, es necesario entrenar en el "conjunto de entrenamiento" y predecir en el "conjunto de prueba" para obtener los resultados, mientras que al mismo tiempo se hace ingeniería de características tanto en los datos de entrenamiento como de prueba, entrenando y prediciendo en la misma canalización única. .

Sin embargo, en los sistemas de la vida real, generalmente tiene datos de entrenamiento y los datos que se van a predecir llegan justo cuando se procesan. En otras palabras, ve la película en un momento y tiene algunas preguntas al respecto más adelante, lo que significa que las respuestas deben ser fáciles y rápidas.

Además, generalmente no es necesario volver a entrenar todo el modelo cada vez que ingresan datos nuevos, ya que el entrenamiento lleva tiempo (puede ser semanas para algunos conjuntos de imágenes) y debería ser lo suficientemente estable con el tiempo.

Es por eso que el entrenamiento y la predicción pueden estar, o incluso deberían estar, claramente separados en muchos sistemas, y esto también refleja mejor cómo aprende un sistema inteligente (artificial o no).

La conexión con el sobreajuste

La separación del entrenamiento y la predicción también es una buena manera de abordar el problema del sobreajuste.

En estadística, el sobreajuste es “la producción de un análisis que se corresponde demasiado estrechamente o exactamente con un conjunto particular de datos y, por lo tanto, puede fallar al ajustar datos adicionales o predecir observaciones futuras de manera confiable”.

texto alternativo de la imagen

La línea verde representa un modelo sobreajustado y la línea negra representa un modelo regularizado. Si bien la línea verde sigue mejor los datos de entrenamiento, depende demasiado de esos datos y es probable que tenga una tasa de error más alta en nuevos datos no vistos, en comparación con la línea negra._

El sobreajuste se ve particularmente en conjuntos de datos con muchas características o con conjuntos de datos con datos de entrenamiento limitados. En ambos casos, los datos tienen demasiada información en comparación con lo que puede validar el predictor, y es posible que algunos de ellos ni siquiera estén vinculados a la variable predicha. En este caso, el ruido mismo podría interpretarse como una señal.

Una buena forma de controlar el sobreajuste es entrenar sobre una parte de los datos y predecir sobre otra parte sobre la que tenemos la verdad de fondo. Por lo tanto, el error esperado en nuevos datos es aproximadamente el error medido en ese conjunto de datos, siempre que los datos con los que entrenemos sean representativos de la realidad del sistema y sus estados futuros.

Entonces, si diseñamos una canalización adecuada de entrenamiento y predicción junto con una división correcta de datos, no solo abordamos el problema de sobreajuste, sino que también podemos reutilizar esa arquitectura para predecir nuevos datos.

El último paso sería controlar que el error en los nuevos datos sea el mismo que se esperaba. Siempre hay un cambio (el error real siempre está por debajo del esperado) y se debe determinar cuál es un cambio aceptable, pero ese no es el tema de este artículo.

Una API REST para predecir

Ahí es donde es útil separar claramente el entrenamiento y la predicción. Si guardamos nuestros métodos de ingeniería de características y los parámetros de nuestro modelo, entonces podemos construir una API REST simple con estos elementos.

texto alternativo de la imagen

La clave aquí es cargar el modelo y los parámetros en el lanzamiento de la API. Una vez iniciada y almacenada en la memoria, cada llamada a la API activa el cálculo de ingeniería de características y el método de "predicción" del algoritmo ML. Ambos suelen ser lo suficientemente rápidos para garantizar una respuesta en tiempo real.

La API puede diseñarse para aceptar un único ejemplo a predecir, o varios diferentes (predicciones por lotes).

Aquí está el código Python/Flask mínimo que implementa este principio, con JSON dentro y fuera de JSON (pregunta dentro, respuesta fuera):

 app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call No need to re-load the model """ # reads the received json jsonfile = request.get_json() res = dict() for key in jsonfile.keys(): # calculates and predicts res[key] = model.predict(doTheCalculation(key)) # returns a json file return jsonify(res) if __name__ == '__main__': # Model is loaded when the API is launched model = pickle.load(open('modelfile', 'rb')) app.run(debug=True)

Tenga en cuenta que la API se puede usar para predecir a partir de nuevos datos, pero no recomiendo usarla para entrenar el modelo. Podría usarse, pero esto complica el código de entrenamiento del modelo y podría ser más exigente en términos de recursos de memoria.

Ejemplo de implementación: bicicletas compartidas

Tomemos como ejemplo un conjunto de datos de Kaggle, bicicletas compartidas. Digamos que somos una empresa de bicicletas compartidas que quiere pronosticar la cantidad de bicicletas alquiladas cada día para administrar mejor el mantenimiento de las bicicletas, la logística y otros aspectos del negocio.

texto alternativo de la imagen

Los alquileres dependen principalmente de las condiciones climáticas, por lo que con el pronóstico del tiempo, esa empresa podría tener una mejor idea de cuándo alcanzarán su punto máximo los alquileres y tratar de evitar el mantenimiento en esos días.

Primero, entrenamos un modelo y lo guardamos como un objeto pickle que se puede ver en el cuaderno Jupyter.

El entrenamiento y el rendimiento del modelo no se tratan aquí, este es solo un ejemplo para comprender el proceso completo.

Luego escribimos la transformación de datos que se realizará en cada llamada a la API:

 import numpy as np import pandas as pd from datetime import date def doTheCalculation(data): data['dayofyear']=(data['dteday']- data['dteday'].apply(lambda x: date(x.year,1,1)) .astype('datetime64[ns]')).apply(lambda x: x.days) X = np.array(data[['instant','season','yr','holiday','weekday','workingday', 'weathersit','temp','atemp','hum','windspeed','dayofyear']]) return X

Esto es solo un cálculo de una variable (día del año) para incluir tanto el mes como el día exacto. También hay una selección de columnas y su respectivo orden a guardar.

Necesitamos, entonces, escribir la API REST con Flask:

 from flask import Flask, request, redirect, url_for, flash, jsonify from features_calculation import doTheCalculation import json, pickle import pandas as pd import numpy as np app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call """ jsonfile = request.get_json() data = pd.read_json(json.dumps(jsonfile),orient='index',convert_dates=['dteday']) print(data) res = dict() ypred = model.predict(doTheCalculation(data)) for i in range(len(ypred)): res[i] = ypred[i] return jsonify(res) if __name__ == '__main__': modelfile = 'modelfile.pickle' model = pickle.load(open(modelfile, 'rb')) print("loaded OK") app.run(debug=True)

Ejecute este programa, servirá la API en el puerto 5000 de forma predeterminada.

Si probamos una solicitud localmente, aún con Python:

 import requests, json url = '[http://127.0.0.1:5000/api/makecalc/](http://127.0.0.1:5000/api/makecalc/)' text = json.dumps({"0":{"instant":1,"dteday":"2011-01-01T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":6,"workingday":0,"weathersit":2,"temp":0.344167,"atemp":0.363625,"hum":0.805833,"windspeed":0.160446}, "1":{"instant":2,"dteday":"2011-01-02T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":3,"workingday":0,"weathersit":2,"temp":0.363478,"atemp":0.353739,"hum":0.696087,"windspeed":0.248539}, "2":{"instant":3,"dteday":"2011-01-03T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":1,"workingday":1,"weathersit":1,"temp":0.196364,"atemp":0.189405,"hum":0.437273,"windspeed":0.248309}})

La solicitud contiene toda la información que se envió al modelo. Por tanto, nuestro modelo responderá con una previsión de alquiler de bicicletas para las fechas especificadas (aquí tenemos tres de ellas).

 headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} r = requests.post(url, data=text, headers=headers) print(r,r.text) <Response [200]> { "0": 1063, "1": 1028, "2": 1399 }

¡Eso es todo! Este servicio podría utilizarse fácilmente en la aplicación de cualquier empresa, para la planificación del mantenimiento o para que los usuarios estén al tanto del tráfico de bicicletas, la demanda y la disponibilidad de bicicletas de alquiler.

Poniendolo todo junto

El principal defecto de muchos sistemas de aprendizaje automático, y especialmente de PoC, es mezclar entrenamiento y predicción.

Si se separan cuidadosamente, las predicciones en tiempo real se pueden realizar con bastante facilidad para un MVP, a un costo y esfuerzo de desarrollo bastante bajos con Python/Flask, especialmente si, para muchas PoC, se desarrolló inicialmente con Scikit-learn, Tensorflow, o cualquier otra biblioteca de aprendizaje automático de Python.

Sin embargo, es posible que esto no sea factible para todas las aplicaciones, especialmente aquellas en las que la ingeniería de características es pesada o las aplicaciones que recuperan la coincidencia más cercana que necesitan tener los datos más recientes disponibles en cada llamada.

En cualquier caso, ¿necesita ver películas una y otra vez para responder preguntas sobre ellas? ¡La misma regla se aplica al aprendizaje automático!