-
Notifications
You must be signed in to change notification settings - Fork 0
Home
- Espinoza Herrera, Marcela
- Sandoval Huamaní, Adrian
- Guillén Rodriguez, Fernando
- Lindo Peña, Ximena Nicolle
El objetivo de este proyecto es desarrollar un sistema eficiente de indexación y búsqueda que permita manejar grandes volúmenes de datos. Esto se logrará mediante la implementación de un índice invertido y un índice multidimensional, optimizando tanto la construcción como la ejecución de consultas.
El dominio de datos utilizado en este proyecto consiste en un conjunto de canciones de Spotify, que incluye información tanto textual como no textual. Los campos textuales incluyen el nombre de la pista, el artista, las letras, el nombre del álbum, el nombre de la lista de reproducción, el género, el subgénero y el idioma. Los campos no textuales incluyen métricas como la popularidad de la pista, el ID del álbum, la fecha de lanzamiento del álbum, el ID de la lista de reproducción, así como características de audio como la capacidad de baile, la energía, la clave, la sonoridad, el modo, la habla, la acústica, la instrumentalidad, la vivacidad, la valencia, el tempo y la duración en milisegundos.
La indexación es crucial en este contexto debido a la gran cantidad de datos textuales y la necesidad de realizar búsquedas eficientes y precisas. Sin un índice adecuado, buscar información relevante en un gran volumen de datos sería extremadamente lento y poco práctico. El uso de un índice invertido permite acceder rápidamente a los documentos que contienen los términos de consulta, mejorando significativamente el rendimiento de las búsquedas y proporcionando resultados más precisos. Además, la indexación nos ayuda a gestionar y consultar de manera eficiente los datos textuales, permitiendo realizar búsquedas precisas basadas en el campo "text", que es la concatenación de todos los campos textuales. Esto mejora la relevancia y precisión de los resultados de búsqueda al enfocarse en el contenido textual de las canciones.
El preprocesamiento es una etapa clave en la construcción de nuestro índice invertido porque permite normalizar y limpiar los datos textuales, asegurando que el proceso de indexación sea eficiente y los resultados de búsqueda sean precisos y relevantes. Al transformar el texto en una forma más manejable y consistente, se mejora la capacidad del sistema para identificar y recuperar información pertinente. En este proyecto, se llevaron a cabo los siguientes pasos para nuestro preprocesamiento:
-
Concatenación de Campos Textuales: Se concatenaron todos los campos textuales relevantes en un solo texto por fila, utilizando el carácter especial "@" como delimitador para evitar conflictos con comas presentes en las letras de las canciones. Esto incluye campos como el nombre de la pista, el artista, las letras, el nombre del álbum, el nombre de la lista de reproducción, el género, el subgénero y el idioma. La columna resultante, denominada "text", contiene estos datos combinados para cada fila del dataset.
-
Tokenización: El texto concatenado se dividió en palabras individuales o tokens. Este paso es crucial para analizar y procesar cada palabra de manera independiente. La columna resultante, denominada "tokens", contiene una lista de palabras extraídas del texto concatenado.
-
Filtrado de Stopwords: Se eliminaron las palabras comunes y de poco valor semántico conocidas como stopwords (por ejemplo, "el", "la", "de"). Esto ayuda a reducir el ruido en los datos y enfocar el análisis en las palabras más significativas. La columna resultante, denominada "filtered_tokens", contiene la lista de palabras después de eliminar las stopwords.
-
Stemming: Se aplicó un algoritmo de stemming para reducir las palabras a su raíz o forma básica. Por ejemplo, las palabras "correr", "corriendo" y "corrí" se reducen a la raíz común "corr". Esto ayuda a unificar las diferentes formas de una palabra y mejora la relevancia de las búsquedas. La columna resultante, denominada "stemmed_tokens", contiene las palabras después de aplicar el stemming.
Resumen del Proceso Completo de Preprocesamiento:
-
Concatenación de Campos Textuales: Se combinan los valores de los campos textuales en una sola cadena para cada fila del dataset.
-
Tokenización: Se divide el texto concatenado en palabras individuales, creando una lista de tokens.
-
Filtrado de Stopwords: Se eliminan las palabras comunes que no aportan mucho significado para el análisis.
-
Stemming: Se reduce cada palabra a su raíz o forma base, lo que ayuda a normalizar las palabras y reducir la dimensionalidad del texto.
-
Construcción del índice invertido en memoria secundaria:
El índice invertido es fundamental para asegurar búsquedas eficientes y rápidas en grandes volúmenes de datos textuales. Para construir el índice invertido en memoria secundaria, realizamos los siguientes estos pasos:
-
Extracción de la Columna Relevante (text):
Antes de construir el índice invertido, es importante aclarar que extragimos solo la columna relevante del dataset que contiene el texto preprocesado. Para ello, se utiliza el script test.py, el cual se encarga de generar un archivo text.csv con la columna "text". Este paso garantiza que el índice no cargue toda la data, sino únicamente la información esencial (relevante) para las búsquedas textuales.
-
Construcción del Índice:
-
Tokenización y Cálculo de TF-IDF Cada documento es tokenizado, y se calcula el TF-IDF (Term Frequency-Inverse Document Frequency) para cada término en el documento. Este paso pondera la importancia de cada término dentro de un documento y en el corpus completo.
-
Estructuración del Índice Los términos tokenizados y sus respectivas ponderaciones TF-IDF se almacenan en una estructura de datos adecuada para consultas eficientes. Esta estructura se guarda en memoria secundaria para manejar grandes volúmenes de datos.
-
Algoritmo SPIMI (Single-pass In-memory Indexing)
El algoritmo SPIMI es utilizado para la construcción del índice invertido en bloques, permitiendo manejar grandes volúmenes de datos sin necesidad de mucha memoria.
-
Pasos del Algoritmo SPIMI:
Procesamiento del Flujo de Tokens: Se procesa el flujo de tokens, contando la frecuencia de términos (term_freq). Construcción del Diccionario: Se construye un diccionario donde cada término apunta a una lista de postings (documentos y frecuencias). Escritura en Disco: Los postings se escriben a archivos temporales para manejar grandes volúmenes de datos sin sobrecargar la memoria. Puntos Fuertes de SPIMI:
-
Eficiencia de Memoria: Al escribir bloques a disco, SPIMI maneja eficientemente grandes volúmenes de datos sin necesitar mucha memoria.
-
Simplicidad: Es relativamente sencillo de implementar y entender.
-
-
-
Ejecución óptima de consultas aplicando Similitud de Coseno
Una vez construido el índice invertido, las consultas se ejecutan aplicando la similitud de coseno entre el vector de la consulta y los vectores de los documentos indexados. Este proceso incluye los siguientes pasos:
- Vectorización de la Consulta: La consulta ingresada por el usuario se tokeniza y se convierte en un vector de términos.
- Cálculo de Similitud: Se calcula la similitud de coseno entre el vector de la consulta y los vectores de los documentos en el índice. Esto implica utilizar los pesos TF-IDF previamente calculados y las normas de los documentos almacenadas.
- Rankeo de Resultados: Los documentos se ordenan según su similitud con la consulta, y se retorna el top-k de documentos más relevantes.
-
Explique cómo se construye el índice invertido en PostgreSQL
El sistema utiliza PostgreSQL para almacenar y recuperar textos mediante índices optimizados y funciones de similitud avanzadas. A continuación se detallan los pasos clave y tecnologías utilizadas:
-
Configuración Inicial y Carga de Datos
Se creó una tabla
songs
para almacenar datos relacionados con canciones, incluyendo un campotext
para las letras de las canciones. Los datos se cargaron desde un archivo CSV. -
Uso de Extensiones PostgreSQL
Se instalaron y configuraron las extensiones
pg_trgm
yunaccent
para mejorar las capacidades de búsqueda. -
Creación de Columna y Índice
Se agregó una columna
tsvector_col
de tipotsvector
y se creó un índice GIN en esta columna para optimizar las consultas de búsqueda de texto completo.ALTER TABLE songs ADD COLUMN tsvector_col tsvector; UPDATE songs SET tsvector_col = to_tsvector('spanish', unaccent(text)); CREATE INDEX idx_tsvector ON songs USING GIN(tsvector_col);
Se utilizó la función ts_rank_cd para calcular la similitud de coseno entre el contenido del tsvector_col y una consulta de texto representada por to_tsquery.
SELECT track_id, track_name, track_artist, ts_rank_cd(tsvector_col, query) AS rank FROM songs, to_tsquery('spanish', 'gola') AS query WHERE tsvector_col @@ query ORDER BY rank DESC;
-
La extracción de características en nuestro proyecto se lleva a cabo siguiendo un proceso detallado y meticuloso para asegurar la máxima precisión en la comparación de pistas musicales. Inicialmente, accedemos a un archivo CSV que contiene información crucial, como el nombre de la pista (track_name) y la URL de la vista previa del track (track_preview), entre otras columnas. Utilizando esta información, descargamos los archivos MP3 correspondientes. Estos archivos de audio se convierten en espectrogramas de Mel, cada uno con 128 características distintivas. Los espectrogramas se guardan como imágenes PNG en una carpeta, con nombres en el formato audio_mel.png para mantener una organización clara.
Una vez que tenemos los espectrogramas en PNG, procedemos a obtener los vectores característicos. Leemos cada imagen PNG, la procesamos y aplicamos una reducción de dimensionalidad utilizando PCA (Análisis de Componentes Principales) con una varianza de 0.95. Esta etapa es crítica, ya que nos permite conservar la mayor parte de la información relevante, asegurando que las características musicales esenciales se mantengan intactas para comparaciones precisas. El uso de PCA de scikit-learn nos ayuda a reducir el ruido y concentrar la información en menos dimensiones. Finalmente, los vectores característicos resultantes se aplanan y se guardan en un archivo CSV. Este archivo contiene más de 1000 columnas, donde cada fila representa un vector característico de una pista, junto con el nombre del archivo de audio correspondiente (audio_track). Este proceso meticuloso asegura que las comparaciones musicales sean eficientes y precisas, destacando las similitudes entre las pistas de manera efectiva.
La búsqueda KNN encuentra los K vecinos más cercanos a un vector de consulta. Utiliza una cola de prioridad para mantener los K elementos más cercanos.
- Inicialización: Se utiliza una cola de prioridad para almacenar los elementos cercanos.
- Cálculo de Distancia: Se calcula la distancia euclidiana entre el vector de consulta y cada punto de datos.
- Gestión de la Cola: Se mantiene solo los K elementos más cercanos en la cola.
def knn_lineal_search(query_vector, data, K):
priority_queue = []
for index, row in data.iterrows():
distance = euclidean_distance(query_vector, row['MFCC_Vector'])
heappush(priority_queue, (-distance, (index, row))) # Usar distancia negativa para heappop más cercano
if len(priority_queue) > K:
heappop(priority_queue)
La búsqueda por rango encuentra todos los puntos dentro de un radio específico alrededor del vector de consulta.
- Inicialización: Se crea una lista para almacenar los resultados.
- Cálculo de Distancia: Se calcula la distancia euclidiana y se verifica si está dentro del radio especificado.
- Ordenamiento: Opcionalmente, los resultados se ordenan por distancia.
def range_lineal_search(query_vector, data, radius, sort_results=True):
results = []
for index, row in data.iterrows():
distance = euclidean_distance(query_vector, row['MFCC_Vector'])
if distance <= radius:
results.append((distance, row))
# Ordenar los resultados por distancia si sort_results es True
if sort_results:
results.sort(key=lambda x: x[0])
return results
El R-Tree es una estructura de datos multidimensional utilizada para almacenar y consultar objetos espaciales. En el contexto de la búsqueda KNN y búsqueda por rango, el R-Tree se utiliza para mejorar la eficiencia en la recuperación de puntos cercanos o dentro de un rango específico.
- Inicialización del Índice: Se configura el índice R-Tree especificando la dimensión de los vectores.
- Construcción del índice: Se insertan los vectores de datos como tuplas en el índice R-Tree, con los bounding box correctamente limitado.
- Consulta KNN: Se utiliza el método nearest del índice para obtener los K vecinos más cercanos.
class RtreeKNN:
def __init__(self, data, index_name, dimension, m = 1):
p = index.Property()
p.dimension = dimension
p.dat_extension = 'data'
p.idx_extension = 'index'
p.buffering_capacity = m
data_file = f'{index_name}.data'
index_file = f'{index_name}.index'
if os.path.exists(data_file):
os.remove(data_file)
print(f"Removed existing data file: {data_file}")
if os.path.exists(index_file):
os.remove(index_file)
print(f"Removed existing index file: {index_file}")
self.idx = index.Index(index_name, properties=p)
if data is not None:
self.data = data
self._build_index()
def _build_index(self):
for i in range(len(self.data)):
point = tuple(self.data.iloc[i])
boundingBox = point + point
self.idx.insert(i, boundingBox)
def knn_search(self, query_vector, K):
query_point = tuple(query_vector)
bounding_box = query_point + query_point
neighbors = list(self.idx.nearest(bounding_box, K, objects=True))
def euclidean_distance(point1, point2):
return np.sqrt(np.sum((np.array(point1) - np.array(point2)) ** 2))
result = []
for neighbor in neighbors:
point = tuple(self.data.iloc[neighbor.id])
distance = euclidean_distance(query_point, point)
result.append((neighbor.id, distance))
return result
La búsqueda por rango con R-Tree encuentra todos los puntos dentro de un radio específico alrededor del vector de consulta.
- Inicialización del Índice: Se configura el índice R-Tree con la dimensión adecuada.
- Inserción de Datos: Los vectores de datos se insertan en el índice.
- Consulta por Rango: Se utiliza el método intersection del índice para obtener los puntos dentro del radio especificado.
class RtreeRangeSearch:
def __init__(self, data, index_name, dimension, m=1):
p = index.Property()
p.dimension = dimension
p.dat_extension = 'data'
p.idx_extension = 'index'
p.buffering_capacity = m
data_file = f'{index_name}.data'
index_file = f'{index_name}.index'
if os.path.exists(data_file):
os.remove(data_file)
print(f"Removed existing data file: {data_file}")
if os.path.exists(index_file):
os.remove(index_file)
print(f"Removed existing index file: {index_file}")
self.idx = index.Index(index_name, properties=p)
if data is not None:
self.data = data
self._build_index()
def _build_index(self):
for i in range(len(self.data)):
point = tuple(self.data.iloc[i])
boundingBox = point + point
self.idx.insert(i, boundingBox)
def range_search(self, query_vector, radius):
query_point = tuple(query_vector)
bounding_box = (
query_point[0] - radius, query_point[1] - radius,
query_point[0] + radius, query_point[1] + radius
)
neighbors = list(self.idx.intersection(bounding_box, objects=True))
def euclidean_distance(point1, point2):
return np.sqrt(np.sum((np.array(point1) - np.array(point2)) ** 2))
result = []
for neighbor in neighbors:
point = tuple(self.data.iloc[neighbor.id])
distance = euclidean_distance(query_point, point)
result.append((neighbor.id, distance))
return result
El Locality-Sensitive Hashing (LSH) es una técnica utilizada para la búsqueda aproximada en espacios de alta dimensión. LSH permite realizar búsquedas rápidas y eficientes al agrupar vectores similares en la misma "cesta" mediante funciones hash específicas. Esta técnica es especialmente útil en contextos donde la maldición de la dimensionalidad hace que las búsquedas exactas sean computacionalmente ineficientes.
-
Función Hash Basada en Proyecciones Aleatorias:
La función hash basada en proyecciones aleatorias mapea vectores a valores hash en función de proyecciones aleatorias. Para cada vector, se calcula un hash basado en la comparación de productos punto con vectores aleatorios.
-
Crear el Índice LSH:
Crear el Índice LSH: El índice LSH organiza los vectores de datos en una estructura hash utilizando funciones hash basadas en proyecciones aleatorias. Este índice facilita la búsqueda rápida al agrupar vectores similares bajo el mismo valor hash.
import numpy as np
def create_lsh_index(data, num_hash_functions, vector_size):
random_vectors = [np.random.randn(vector_size) for _ in range(num_hash_functions)]
lsh_index = {}
for index, row in data.iterrows():
mfcc_vector = row['MFCC_Vector']
hash_value = hash_function(mfcc_vector, random_vectors)
if hash_value not in lsh_index:
lsh_index[hash_value] = []
lsh_index[hash_value].append((index, mfcc_vector))
return lsh_index, random_vectors
-
Crear el Índice LSH:
Búsqueda KNN Usando LSH: La búsqueda KNN con LSH encuentra los K vecinos más cercanos utilizando el índice LSH. Se calcula el hash del vector de consulta y se busca en el "bucket" correspondiente. Si no se encuentran candidatos, se puede explorar hashes vecinos.
def lsh_knn_search(query_vector, lsh_index, random_vectors, K):
query_hash = hash_function(query_vector, random_vectors)
candidate_vectors = lsh_index.get(query_hash, [])
if len(candidate_vectors) == 0:
return []
distances = []
for index, vector in candidate_vectors:
distance = np.linalg.norm(query_vector - vector)
distances.append((distance, index))
distances.sort()
return distances[:K]
-
Crear el Índice LSH:
Búsqueda por Rango Usando LSH: La búsqueda por rango con LSH encuentra todos los vectores dentro de un radio específico alrededor del vector de consulta. Al igual que con la búsqueda KNN, se utiliza el índice LSH para acceder rápidamente a los vectores agrupados bajo el mismo hash.
def lsh_range_search(query_vector, lsh_index, random_vectors, radius):
query_hash = hash_function(query_vector, random_vectors)
candidate_vectors = lsh_index.get(query_hash, [])
results_within_radius = []
for index, vector in candidate_vectors:
distance = np.linalg.norm(query_vector - vector)
if distance <= radius:
results_within_radius.append((distance, index))
return results_within_radius
Describir el diseño de la interfaz gráfica de usuario (GUI), incluyendo:
- Herramientas y frameworks utilizados.
- Estructura de la interfaz.
- Principales funcionalidades.
Sigue estos pasos para configurar y ejecutar correctamente el proyecto.
- Clona el repositorio:
git clone https://github.com/Sandovl0593/InformationRetrieval.git
cd InformationRetrieval
-
Instala las dependencias:
Instala las dependencias necesarias desde el archivo "requirements.txt".
pip install -r requirements.txt
-
Descarga el dataset desde Kaggle:
Descarga el dataset spotify_songs.csv de la plataforma Kaggle. Asegúrate de colocar el archivo descargado spotify_songs.csv en la carpeta "dataset" de tu proyecto.
-
Ejecuta el script de preprocesamiento de texto:
python dataset/textpreprocessor.py
- Ejecuta el script principal para generar archivos reducidos:
python dataset/text.py
- Iniciar el servidor Flask:
flask run
Esto iniciará el servidor correctamente y te proporcionará el enlace donde podrás acceder e interactuar con el proyecto.
Nuestro frontend permite a los usuarios interactuar con el índice invertido y realizar búsquedas de manera sencilla y eficiente. A continuación, se describen los componentes principales de la GUI y su funcionamiento:
-
Mini-manual de usuario
- Ingreso de Consulta: El usuario puede ingresar una frase en lenguaje natural en el campo de búsqueda.
- Selección de Top-K Resultados: El usuario puede especificar cuántos documentos (Top K) desea recuperar.
- Ejecución de Búsqueda: Al presionar el botón de "Consultar"", el sistema procesa la consulta y muestra los resultados.
- Visualización de Resultados: Los resultados se presentan de manera amigable, indicando el tiempo de procesamiento de la consulta.
- Selección del Método de Indexación: El usuario puede elegir entre nuestra propia implementación del índice invertido o el uso de bases de datos PostgreSQL para realizar la búsqueda.
-
Screenshots de la GUI - Parte 1:
-
Pantalla de Inicio:
Al ingresar a la interfaz gráfica, se puede observar que la pantalla inicial cuenta con varios campos importantes. Estos incluyen un campo para ingresar la consulta textual que se desea buscar, una opción para seleccionar el Top K resultados, y un selector para elegir la técnica de indexación (Manual o PostgreSQL). Además, el usuario puede especificar la cantidad de datos sobre los cuales desea realizar la búsqueda, con opciones de 1000, 5000, 10000, 15000, o Todos.
- Resultados de Búsqueda:
Como se puede observar en la imagen a continuación, estamos buscando una canción que contenga la palabra "Hola", utilizando la técnica "Manual", con un Top K de 25 y una cantidad de datos de 1000.
Hemos encontrado una coincidencia para la consulta realizada con la palabra "Hola". La canción encontrada se llama "Me Enamoré" del artista "Jhay Wheeler", como se puede observar en la imagen a continuación.
En la siguiente imagen, podemos ver que el tiempo de ejecución de esta consulta fue de "898.0598 ms".
Por último, en la última imagen podemos confirmar que la letra de la canción "Me Enamoré" del artista Jhay Wheeler efectivamente contiene la palabra "Hola", tal y como se buscó.
-
En esta segunda fase del proyecto, hemos integrado funcionalidades que permiten tanto la búsqueda textual como la búsqueda mediante archivos de audio. Además, ofrecemos al usuario la posibilidad de elegir entre diferentes técnicas de búsqueda, proporcionando opciones para probar con distintas metodologías. Contando y habiendo implementado así las siguientes opciones habilitadas para que el usuario: KNN - Secuencial, KNN - RTree y KNN - HighD.
Screenshots de la GUI - Parte 2:
- Vista para Búsquedas Textuales:
- Vista para Búsquedas por Archivo de Audio:
- My Index: Como podemos observar, al implementar la tecnica Manual (MyIndex) el tiempo de ejecución para procesar la consulta "Hola" fue de "898.0598 ms".
KNN - Secuencial:
- Interpretación:
La gráfica indica que el tiempo de búsqueda usando KNN con k=8 aumenta de forma lineal con el tamaño de la base de datos. Esto muestra que el tiempo es proporcional al número de canciones, sugiriendo que el algoritmo es predecible pero puede volverse menos eficiente con datasets grandes, lo que sugiere la necesidad de optimizaciones.
KNN - HighD:
- Interpretación:
La gráfica muestra que a medida que aumenta el número de canciones en la base de datos, el tiempo requerido para realizar una búsqueda utilizando el algoritmo KNN-HighD con k=8 también incrementa. Esta tendencia lineal y ascendente indica que el algoritmo KNN-HighD, al operar en alta dimensión, presenta un tiempo de búsqueda proporcional al tamaño de la base de datos, reflejando una mayor complejidad computacional y tiempos de respuesta más largos a medida que se incrementa la cantidad de canciones. Además, por la experimentación realizada, se estableció el hash en 7 para evitar respuestas múltiples, garantizando así cero coincidencias y optimizando la precisión de las búsquedas.
KNN - Secuencial vs KNN - HighD:
- Interpretación:
En la comparación de tiempos de ejecución entre las implementaciones KNNSeq y HighD, considerando la conversión de audio MP3 a espectrograma de Mel y la extracción de características, HighD demuestra ser significativamente más eficiente. Para todas las cantidades de canciones probadas (2000, 4000, 6000, 8000, 10000 y 13000), HighD siempre presenta tiempos menores que KNNSeq. Además, a medida que aumenta el número de canciones, la diferencia en tiempo de ejecución entre ambas implementaciones se incrementa, evidenciando que HighD escala mejor con un mayor volumen de datos. Esto sugiere que HighD es una opción más adecuada para manejar grandes conjuntos de datos de audio.
Durante la experimentación con la implementación de knnrtree
, surgieron varios desafíos significativos relacionados con el manejo de grandes volúmenes de datos y la alta dimensionalidad de los vectores de características. Uno de los problemas principales fue la forma en que la librería rtree
gestiona los punteros y la memoria. Python arrojaba errores de memoria debido a la incapacidad de rtree
para manejar eficientemente la creación de índices con vectores de alta dimensionalidad, lo que resultó en constantes fallos durante los experimentos.
La extracción de características se realizó utilizando espectrogramas de Mel, los cuales proporcionan una representación detallada de la información de los audios. Sin embargo, esta detallada representación resulta en vectores de características de gran dimensionalidad, incrementando significativamente los requerimientos de hardware. Este incremento en la dimensionalidad de los vectores exacerbó los problemas de hardware y sobrecargó la capacidad del sistema para crear índices eficientes utilizando rtree
.
Específicamente, los índices rtree
no toleran bien grandes cantidades de vectores de alta dimensionalidad debido a su estructura y metodología de manejo de datos espaciales. Según la teoría, los índices rtree
son efectivos para datos de baja a mediana dimensionalidad, pero su rendimiento y capacidad se degradan rápidamente en presencia de datos de alta dimensionalidad, fenómeno conocido como la "maldición de la dimensionalidad". Esta degradación es un problema esperado y documentado, lo que hace que rtree
no sea la mejor opción para este tipo de datos.
En contraste, los métodos secuenciales (seq
) pueden manejar mejor grandes dimensionalidades, ya que no dependen de estructuras de índice que pueden sobrecargarse. De hecho, es esperable que para grandes dimensionalidades, los métodos de búsqueda secuenciales superen a rtree
en términos de estabilidad y rendimiento, aunque a costa de tiempos de procesamiento más largos. Este contraste subraya las limitaciones de rtree
y la necesidad de considerar alternativas más adecuadas según el contexto y las características de los datos.
A pesar de varios intentos y ajustes, no se logró superar las limitaciones inherentes de rtree
en este contexto. Esta experiencia destaca la necesidad de contar con recursos más potentes y métodos de indexación más adecuados para manejar eficientemente la extracción y el procesamiento de datos de audio a gran escala utilizando espectrogramas de Mel.
Finalmente, como conclusión del proyecto, se puede afirmar que la primera parte fue exitosa y se lograron los objetivos propuestos.Esto evidencia que HighD es una opción viable y efectiva para manejar grandes conjuntos de datos de audio. Sin embargo, es importante notar que los buckets generados tenían un espacio muy pequeño, resultando en una gran cantidad de archivos JSON. Aunque esto mejoró los tiempos de ejecución, aumentó considerablemente el espacio de memoria ocupado.
En la segunda parte del proyecto, se enfrentaron desafíos adicionales, principalmente relacionados con las limitaciones del hardware. La implementación de knnrtree encontró problemas de memoria debido a la forma en que la librería rtree
maneja los punteros y al tamaño considerable de los vectores de características. Estos factores impidieron la creación exitosa del índice y subrayaron la necesidad de contar con un sistema de hardware más robusto para procesar grandes volúmenes de datos de manera eficiente. La alta dimensionalidad de los vectores de características extraídos de los espectrogramas de Mel fue un factor determinante en estos problemas, ya que rtree
no tolera bien grandes cantidades de datos de alta dimensionalidad.
Durante la experimentación para la extracción de características, primero se usaron MFCC, pero no se obtuvo la eficacia deseada. Inicialmente, se intentó con una dimensión de 20 y se incrementó gradualmente hasta llegar a la configuración completa. A pesar de esto, los resultados fueron insatisfactorios, con una baja coincidencia entre los tracks. Se decidió entonces usar un método más completo como el Mel, que proporcionó mejores resultados. Esta diferencia es congruente con la teoría, ya que se observó que MFCC se enfoca más en las voces, mientras que Mel se enfoca más en el ritmo de fondo.
Además, se realizó una experimentación con una playlist de solo inglés, lo que reveló que mientras MFCC no da buenos resultados al trabajar con diferentes idiomas, Mel sí lo hace. Los resultados más cercanos en las búsquedas con Mel no solo coincidían en la música, sino también en el idioma (inglés con inglés, o posiblemente otro idioma de origen latino), lo que sugiere una mayor robustez en la detección de similitudes en los datos de audio.
Para superar estas limitaciones, se propone explorar métodos alternativos para la extracción de características que puedan ofrecer una mejor relación entre la calidad de la información y los requisitos de hardware. Además, es esencial destacar la importancia de la experimentación cuando se trabaja con métodos ya implementados. Ajustar y experimentar con los parámetros específicos para cada conjunto de datos es crucial para optimizar los resultados y mejorar la precisión de los modelos.