Despliegue en local¶
En este documento veremos cómo podemos desplegar nuestros modelos utilizando nuestra propia infraestructura de recursos. De esta forma, configuraremos un servidor web para desarrollar una API que responda a peticiones de clientes que harán consultas sobre el modelo, y responderemos enviando la información en formato JSON, como hacen muchas de las webs de modelos pre-entrenados que hemos visto en otros documentos.
1. Servidores disponibles¶
A la hora de desplegar nuestro modelo Keras/TensorFlow (o PyTorch) sobre un servidor, existen distintas alternativas en Python que podemos emplear, como por ejemplo:
- Flask: es un framework ligero, flexible y fácil de usar, con una integración sencilla con modelos de IA.
- Django: es otro framework más completo (y complejo), que permite desarrollar distintos tipos de aplicaciones web, aunque su curva de aprendizaje no es tan sencilla.
- FastAPI: otro framework ligero que permite construir APIs en Python
En estos apuntes nos centraremos en configurar y utilizar el framework Flask. Aquí tenemos su web oficial, con ciertas nociones básicas de uso.
2. Primeros pasos con Flask¶
Para poder utilizar Flask debemos instalarlo como un paquete del sistema:
pip install Flask
Esto instalará además otras dependencias adicionales, como werkzeug, que se utiliza para establecer una interfaz de comunicación entre el servidor y los clientes que se conecten a él.
Un primer ejemplo sencillo de prueba podría ser éste:
from flask import Flask
app = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def hola():
return "Hola!"
Expliquemos algunas de las líneas de este código:
- La línea
app = Flask(__name__)
inicializa la aplicación. El parámetro__name__
se utiliza para que Flask localice en el módulo actual los recursos que necesite (ficheros estáticos, etc). - La línea
@app.route
define un decorador, que indica que la función siguientehola
se activará cuando accedamos a la ruta raíz"/"
del servidor. El parámetromethods
indica a qué método(s) HTTP responde el enrutador.
2.1. Puesta en marcha del servidor¶
Suponiendo que hayamos llamado prueba.py
a nuestro servidor, para ponerlo en marcha podemos ejecutar este comando desde terminal:
flask --app prueba run
Después podemos acceder a http:localhost:5000 para ver el mensaje de Hola (por defecto Flask escucha en dicho puerto 5000 al iniciar). Una forma alternativa de ponerlo en marcha es con un código como este:
if __name__ == '__main__':
app.run(port=80)
Notar que el método run
admite un parámetro port
para poder establecer el puerto por el que escuchar. Bastaría entonces con ejecutar un programa como éste con el correspondiente comando python:
python prueba.py
3. Procesamiento de peticiones¶
Para recoger datos que nos lleguen en las peticiones de los clientes (campos de formularios, o incluso ficheros), necesitamos utilizar el elemento request
de Flask, así que lo importaremos también al inicio:
from flask import Flask, request
...
La recogida de datos depende de por dónde vengan.
3.1. Recogida de datos por GET¶
Si vienen en la propia URL en formato get (por ejemplo, recurso?nombre=valor), entonces debemos acceder a request.args.get
y obtener el dato por su nombre:
dato = request.args.get('nombre', '')
Nota
El segundo parámetro del método get
permite indicar un valor por defecto en caso de que no se encuentre el parámetro buscado.
3.2. Recogida de datos por POST¶
El envío de datos por POST no se hace en la propia línea de la URL, sino en el cuerpo de la petición. En este caso podemos utilizar el mapa request.form
para acceder a los campos. También podemos verificar previamente si la petición es POST:
def funcion():
if request.method == 'POST':
login = request.form['login']
...
Si estamos subiendo algún fichero podemos acceder a él por su nombre de campo de formulario en el mapa request.files
. De forma opcional también podemos guardarlo en la ubicación que queramos, usando el método save
del propio objeto. Podemos utilizar el nombre original del fichero con su propiedad filename
.
fichero = request.files['nombre_fichero']
fichero.save('/home/usuario/ficheros/' + fichero.filename)
4. Envío de la respuesta¶
Aunque Flask puede utilizarse para enviar distintos tipos de respuesta, e incluso renderizar páginas HTML o vistas jerarquizadas en plantillas, aquí lo utilizaremos simplemente para enviar contenido en formato JSON, que pueda ser utilizado por cualquier cliente que acceda al servidor para obtener respuestas del modelo que estemos utilizando.
Para poder trabajar con datos en formato JSON podemos incorporar el módulo jsonify
de Flask:
from flask import Flask, request, jsonify
...
Una vez hecho esto podemos transformar un diccionario Python en cadena JSON fácilmente:
def funcion():
datos = {
'usuario': 'nacho',
'edad': 45
}
return jsonify(datos)
Sin embargo, Flask es capaz de convertir a formato JSON directamente un diccionario, así que podemos devolver directamente el diccionario (return datos
en el ejemplo anterior).
5. Cuestiones adicionales¶
En este apartado veremos algunas cuestiones adicionales que nos pueden venir bien a la hora de trabajar con Flask, como la gestión de orígenes cruzados (cross-origins), o la definición de plantillas y vistas.
5.1. Gestión de CORS en Flask¶
Si queremos acceder desde una aplicación remota a un servidor, tenemos que tener presente el problema de CORS (Cross-Origin Resource Sharing, compartición de recursos entre orígenes cruzados). Dicho en cristiano, hay que lidiar con la posibilidad de que a nuestro servidor accedan diferentes tipos de aplicaciones clientes de distintos orígenes: Angular, Java, .NET o cualquier otra tecnología, sin necesidad de conectarnos con un navegador al propio servidor para ver el contenido.
Por defecto muchos servidores deshabilitan la opción de CORS, con lo que no podemos acceder a ellos salvo por un navegador que se conecte a la URL del servidor. Para evitar este problema en Flask debemos instalar un paquete adicional llamado flask_cors
:
pip install flask_cors
Después debemos configurar la aplicación Flask con esta librería, y añadir el decorador @cross-origin()
en los enrutadores que admitan CORS. Así quedaría nuestro ejemplo inicial:
from flask import Flask
from flask_cors import CORS, cross_origin
app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
@app.route("/", methods=['GET', 'POST'])
@cross_origin()
def hola():
return "Hola!"
- La línea
cors = CORS(app)
habilita CORS en toda la aplicación. Alternativamente, si sólo queremos habilitarlo en ciertos servicios, usamos la anotacióncross_origin()
que se muestra en el ejemplo (esta anotación no es necesaria si hemos usado la configuración global). - La línea
app.config['CORS_HEADERS'] = 'Content-Type'
permite que se envíe la cabecera Content-Type en las peticiones CORS, lo que puede resultar útil para que el servidor reciba específicamente datos en formato JSON, o formularios, o imágenes, por ejemplo - Existen otras opciones de configuración más o menos complejas, dependiendo de qué queremos permitir y desde qué servicios. Pero no entraremos en detalles tan específicos en este documento.
5.2. Gestión de vistas y plantillas¶
Flask, al igual que otros frameworks de desarrollo web como Express o Laravel, permite definir vistas y plantillas. Esto da la posibilidad de devolver contenido HTML desde Flask definiendo una estructura jerarquizada de páginas, de forma que todas puedan compartir un contenido común a partir de ciertas plantillas base.
No profundizaremos más en este aspecto en estos apuntes, pero dejamos aquí un enlace a la documentación oficial que trata el tema.
5.3. Puesta en producción¶
La forma de iniciar el servidor que hemos visto en un apartado anterior nos va a servir para el período de pruebas y desarrollo. De hecho, podremos ver un mensaje al arrancar el servidor que nos indica que esa no es la forma recomendada de poner en marcha el servidor en producción.
Para el despliegue en producción tenemos varias alternativas, y algunas de ellas las podemos consultar en la documentación oficial. Por ejemplo, podemos instalar un servidor WSGI (Web Server Gateway Interface) que haga de pasarela con Flask, como puede ser waitress
:
pip install waitress
Después de eso podemos lanzar nuestra aplicación en dicho servidor de este modo:
from flask import Flask, request
...
if __name__ == '__main__':
from waitress import serve
# El parámetro 0.0.0.0 indica que la aplicación estará disponible para
# cualquier dirección IP en el servidor (útil si queremos acceder desde
# fuera del servidor local)
serve(app, host='0.0.0.0', port=80)
Ejercicio 1
Utiliza el modelo desarrollado en esta sesión para desarrollar un servidor Flask que determine cómo de aceptable es un vehículo en base a sus características (precio de venta, nivel de seguridad, etc). Accede al modelo guardado pasándole la información que llegue en el cuerpo de la petición (POST), recoge el resultado y devuélvelo en forma de diccionario en formato JSON. Para probar el servidor puedes utilizar este cliente de prueba y completar el código faltante para mostrar el resultado.
Nota
La plantilla cliente incluye un modelo de ejemplo que puedes usar. Ten en cuenta que el orden en que envía las probabilidades de cada categoría de salida es Aceptable, Bueno, Inaceptable y Muy bueno.
Ejercicio 2
Utiliza el modelo desarrollado en esta sesión para desarrollar un servidor que ayude a identificar personas con y sin mascarilla. Accede al modelo guardado pasándole una foto de una persona en la petición (POST), recoge el resultado y muestra en la página el resultado y su probabilidad o grado de confianza alcanzado. Puedes emplear esta plantilla cliente como base, y este modelo guardado de la solución del ejercicio original. En la plantilla falta completar el código para recoger la petición y mostrarla.
Nota
Si utilizas el modelo facilitado, debes tener en cuenta que proporciona una estimación < 0.5 si la persona lleva mascarilla, o mayor en caso contrario.