# Búsqueda Semántica: Un Servicio Web de Similitud de Oraciones con Flask y Sentence Transformers en Python En este artículo, exploraremos una **prueba de concepto** del desarrollo de un "servicio web[^1]" que utiliza Flask y Sentence Transformers para calcular la similitud entre dos oraciones. Este **experimento** tiene como objetivo implementar una funcionalidad de búsqueda semántica, explorando cómo la similitud de oraciones puede mejorar la precisión y relevancia de los resultados en un futuro buscador de productos y precios [Buscador de precios](https://prices.mutiitu.com) . Descubramos cómo este enfoque puede contribuir a una experiencia de búsqueda más avanzada y contextualizada. ## Configuración del Entorno: El primer paso crucial en el desarrollo de nuestro servicio web es configurar el entorno de desarrollo de manera efectiva. Aquí, hemos adoptado un enfoque que asegura un ambiente organizado y escalable para nuestro proyecto usando entornos virtuales. ### Entornos Virtuales (Virtual Environment): La utilización de entornos virtuales es esencial para evitar conflictos entre dependencias de diferentes proyectos. Hemos creado un entorno virtual utilizando la herramienta virtualenv. Esto garantiza que las bibliotecas y dependencias específicas de nuestro proyecto estén aisladas de las instaladas globalmente en el sistema. ```bash # Creación del entorno virtual python -m venv venv # Activación del entorno virtual (Linux) source venv/bin/activate # Activación del entorno virtual (Windows) venv\Scripts\activate ``` ### Requerimientos del Proyecto (requirements.txt): El archivo requirements.txt enumera todas las dependencias necesarias para nuestro proyecto. Esto incluye bibliotecas como Flask, Sentence Transformers y Levenshtein. Al ejecutar pip install -r requirements.txt, todas estas dependencias se instalan automáticamente en nuestro entorno virtual. ```bash # Instalación de dependencias desde requirements.txt pip install -r requirements.txt ``` ### Docker y Variables de Entorno (.env): Para garantizar la portabilidad y la gestión eficiente de variables de entorno, hemos adoptado Docker. Además, el uso de un archivo .env nos permite definir variables específicas de configuración, como el entorno (development o production). ```bash # Archivo .env FLASK_ENV=development SECRET_KEY=tu_clave_secreta ``` * A través de Docker, hemos creado un contenedor que encapsula nuestro servicio web junto con todas sus dependencias. El archivo .env se utiliza para configurar variables específicas de entorno que pueden variar según el entorno de ejecución. ```yaml # Ejemplo de Dockerfile FROM python:3.8 WORKDIR /app COPY . . # Instalación de dependencias RUN pip install -r requirements.txt CMD ["python", "app.py"] ``` ## Descripción del Código: En esta sección, vamos a explorar el código fuente de nuestro servicio web de similitud de oraciones. Entenderemos cómo está estructurado el código y cómo realiza las tareas clave para proporcionar resultados precisos. ### Estructura del Proyecto: La organización del código es fundamental para la legibilidad y el mantenimiento. Nuestro proyecto sigue una estructura común de aplicaciones Flask. ```lua proyecto/ |-- venv/ |-- app/ | |-- __init__.py | |-- config.py | |-- app.py | |-- requirements.txt |-- Dockerfile |-- .env ``` * En la carpeta app, encontramos el código principal de la aplicación, un archivo de configuración (config.py), y el punto de entrada (app.py). El archivo .env y requirements.txt los utilizaremos para la configuración del entorno. ##### Configuración de la Aplicación: * El archivo config.py contiene configuraciones específicas de la aplicación, como configuraciones de desarrollo y producción. Utilizamos variables de entorno para adaptar la configuración según el entorno de ejecución. ```python # config.py class Config: DEBUG = False SECRET_KEY = 'tu_clave_secreta' class DevelopmentConfig(Config): DEBUG = True class ProductionConfig(Config): DEBUG = False ``` ### Código Principal (app.py): app.py es el punto de entrada principal de nuestra aplicación Flask. Aquí definimos rutas, manejadores y la lógica de negocio. ```python # app.py from flask import Flask, request, jsonify from sentence_transformers import SentenceTransformer, util import Levenshtein import os from config import config import numpy as np app = Flask(__name__) # Cargar el modelo de embeddings model = SentenceTransformer('../hiiamsid/sentence_similarity_spanish_es') @app.route('/similarity', methods=['POST']) def calculate_similarity(): try: # Obtener las oraciones desde la solicitud data = request.json sentence1 = data['sentence1'] sentence2 = data['sentence2'] # Obtener el algoritmo de similitud (por defecto, usa cosine) similarity_algorithm = data.get('similarity_algorithm', 'cosine') # Calcular la similitud usando el algoritmo especificado if similarity_algorithm == 'cosine': embeddings = model.encode([sentence1, sentence2]) measure = data.get('measure', 'cos_sim') if measure == 'cos_sim': similarity_score = util.pytorch_cos_sim(embeddings[0], embeddings[1]).item() elif measure == 'dot_score': similarity_score = util.dot_score(embeddings[0], embeddings[1]).item() elif measure == 'heuristic': # Lógica específica para el caso heurístico similarity_score = calcular_similitud_heuristica(embeddings) else: measure = 'unknown' similarity_score = util.dot_score(embeddings[0], embeddings[1]).item() elif similarity_algorithm == 'levenshtein_distance': similarity_score = Levenshtein.distance(sentence1, sentence2) else: return jsonify({'error': 'Algoritmo de similitud no válido'}), 400 return jsonify({'similarity_score': similarity_score}) except Exception as e: return jsonify({'error': str(e)}), 500 def calcular_similitud_heuristica(embeddings): # Lógica específica para el caso heurístico # ... if __name__ == '__main__': app.run(debug=config.DEBUG) ``` * En este archivo, manejamos las solicitudes POST a la ruta /similarity, calculamos la similitud de las oraciones y devolvemos el resultado en formato JSON. * Esta estructura modular y organizada del código permite una fácil comprensión y mantenimiento a medida que el proyecto evoluciona. Además, la lógica de la aplicación está claramente definida y separada en funciones, lo que facilita su expansión y mejora. ### Carga del modelo de embeddings de Sentence Transformers. En esta sección, exploraremos cómo nuestro servicio web carga y utiliza el modelo de embeddings proporcionado por Sentence Transformers para realizar comparaciones de similitud entre oraciones. #### Uso de Sentence Transformers: Sentence Transformers es una biblioteca especializada en proporcionar embeddings de oraciones. En nuestro servicio, utilizamos esta biblioteca para traducir oraciones a vectores numéricos densos en un espacio semántico. ```python # app.py from sentence_transformers import SentenceTransformer, util # ... # Cargar el modelo de embeddings model = SentenceTransformer('../hiiamsid/sentence_similarity_spanish_es') ``` * Aquí, inicializamos un objeto model de la clase SentenceTransformer y le proporcionamos el nombre del modelo preentrenado. En este caso, utilizamos el modelo español de [Siddhartha Shrestha](https://huggingface.co/hiiamsid), que se encuentra en la ruta 'hiiamsid/sentence_similarity_spanish_es' [sentence_similarity_spanish_es](https://huggingface.co/hiiamsid/sentence_similarity_spanish_es) en el hub de modelos de Sentence Transformers. * Cabe destacar que Sentence Transformers ofrece una variedad de modelos preentrenados en diferentes idiomas. Puedes explorar otros modelos disponibles en el hub de modelos de [Sentence Transformers](https://www.sbert.net/docs/pretrained_models.html) para adaptar la aplicación según tus necesidades y el contexto de tu proyecto. #### Generación de Embeddings: * Una vez cargado, podemos utilizar el modelo para codificar oraciones y obtener embeddings. ```python # app.py @app.route('/similarity', methods=['POST']) def calculate_similarity(): try: # Obtener las oraciones desde la solicitud data = request.json sentence1 = data['sentence2'] sentence2 = data['sentence1'] # Obtener el algoritmo de similitud (por defecto, usa cosine) similarity_algorithm = data.get('similarity_algorithm', 'cosine') # Calcular la similitud usando el algoritmo especificado if similarity_algorithm == 'cosine': embeddings = model.encode([sentence1, sentence2]) # Resto de la lógica... ``` * En la función calculate_similarity, llamamos a model.encode con una lista de oraciones. Esto devuelve una matriz de embeddings correspondientes a cada oración. * Selección del Algoritmo de Similitud: Dependiendo de la solicitud del usuario, seleccionamos el algoritmo de similitud adecuado. En este caso, utilizamos medidas como la similitud del coseno (cos_sim), similitud del coseno a nivel de pares (pairwise_cos_sim), y otros. ```python # app.py if measure == 'cos_sim': similarity_score = util.pytorch_cos_sim(embeddings[0], embeddings[1]).item() elif measure == 'pairwise_cos_sim': similarity_score = util.pairwise_cos_sim(embeddings[0], embeddings[1]).item() elif measure == 'dot_score': similarity_score = util.dot_score(embeddings[0], embeddings[1]).item() elif measure == 'heuristic': similarity_score = calcular_similitud_heuristica(embeddings) else: measure = 'unknown' similarity_score = util.dot_score(embeddings[0], embeddings[1]).item() ``` Cada medida de similitud se calcula utilizando las funciones proporcionadas por la biblioteca util de Sentence Transformers. La carga y uso eficiente del modelo de embeddings son fundamentales para el rendimiento y la precisión de nuestro servicio web. La modularidad de Sentence Transformers facilita la integración y adaptación de modelos preentrenados para tareas específicas. ### Ejemplos de Uso: En esta sección, proporcionaremos ejemplos concretos de cómo utilizar nuestro servicio web para calcular la similitud entre oraciones. Utilizaremos la herramienta curl para realizar solicitudes HTTP a nuestra API. #### Ejemplo de Solicitud: ```bash curl -X POST http://prices.mutiitu.com/similarity \ --header "Content-Type: application/json" \ --data '{"sentence1":"crema de calabaza","sentence2":"pure de calabaza con jamón", "similarity_algorithm":"cosine","measure":"cos_sim"}' ``` * Explicación: * curl: La herramienta de línea de comandos utilizada para realizar solicitudes HTTP. * -X POST: Especifica que estamos realizando una solicitud de tipo POST. * http://prices.mutiitu.com/similarity: La URL de nuestro servicio web y el punto final para calcular la similitud. * --header "Content-Type: application/json": Indica que el contenido de la solicitud está en formato JSON. * --data '{"sentence1":"Hoy estoy contento","sentence2":"Hoy estoy muy alegre", "similarity_algorithm":"cosine","measure":"cos_sim"}': Los datos de la solicitud en formato JSON, que incluyen las dos oraciones que queremos comparar, el algoritmo de similitud (cosine), y la medida de similitud (cos_sim). ### Conclusiones y Perspectivas Futuras: En conclusión, este pequeño experimento demuestra el potencial de la tecnología de Procesamiento del Lenguaje Natural (NLP) y el uso de modelos de embeddings de oraciones para calcular la similitud semántica entre ellas. Algunos puntos clave a destacar son: #### Logros 1 -Implementación Efectiva: El uso de Flask como marco web y Sentence Transformers para generar embeddings de oraciones ha demostrado ser efectivo y sencillo de implementar. 2- Variedad de Algoritmos: La capacidad de utilizar diferentes algoritmos de similitud, como el coseno y la distancia de Levenshtein, proporciona flexibilidad para abordar diversos casos de uso. #### Perspectivas Futuras: 1 - Integración con Buscador de Productos: Este experimento sirve como un primer paso hacia el objetivo de construir un buscador de productos basado en similitud semántica. [Buscador de precios](https://prices.mutiitu.com) 2 -Mejoras en el Modelo: La calidad de las comparaciones podría mejorarse explorando modelos de embeddings más avanzados o ajustando los parámetros del modelo actual o generando nuevos. [^1]: Código fuente. Si los lectores quieren acceder al código fuente del proyecto, pueden hacerlo a través del siguiente enlace: [Sentence Distance Server en GitHub](https://github.com/eurekatop/sentece-distance-server).