DiasporeUnizar / TEG

TEG library
GNU General Public License v2.0
4 stars 6 forks source link

Estudiar y adaptar métricas a implementación dispersa #8

Closed 844759 closed 1 month ago

844759 commented 2 months ago

Hola Simona,

He estado analizando si se podría utilizar operaciones de matrices dispersas en las métricas, en concreto para el caso de Hamming he visto que al utilizar operaciones de matrices dispersas evitando la conversión a densas, el mejor tiempo que he obtenido es de un "time2metrics" de 224.510091 ms, en cambio para la versión original de matrices dispersas obtengo un 194.076013 ms bajo las mismas circunstancias.

Por ello parece que es mejor en términos de rendimiento pasar a matrices densas para el calculo de Hamming puesto las operaciones de matrices densas parecen estar ya optimizadas para este tipo de cálculos.

Un saludo,

Ángel

simber72 commented 2 months ago

Hola Angel, quizá Hamming y Cosine son las métricas más "especiales". Las demás todas se basan en normalizar los datos de cada matriz: donde en la versión original, la normalización pasa las matrices NxN en array unidimensionales N^2. En particular, se puede pensar en reemplazar las líneas de código (en graph_comparison.py):

  1. edges1 = self._graph1.get_matrix().toarray().flatten()
  2. edges2 = self._graph2.get_matrix().toarray().flatten()

para normalizar directamente las matrices en formato csr con las api de la librería scipy.sparse....

Lo hablamos mañana. Un saludo, Simona

844759 commented 1 month ago

Hola Simona,

He estado analizando y midiendo tiempos y parece que para las métricas no es buena idea utilizar matrices dispersas, puesto que en ejemplos como Dice o Jaccard obtenemos tiempos mucho más altos para computar las métricas, para ello había modificado la normalización para obtenerlo en formato disperso directamente:

def _normalize_matrices(self):
        """
        Flatten the matrices of the two graphs and normalize them
        """
        # Get the two matrices in CSR format (sparse matrices)
        edges1 = self._graph1.get_matrix()  # CSR format
        edges2 = self._graph2.get_matrix()  # CSR format

        # Sum the entries of each matrix
        sum1 = edges1.sum()
        sum2 = edges2.sum()

        # Normalize the matrices by dividing each entry by the sum
        edges1 = edges1.multiply(1.0 / sum1)
        edges2 = edges2.multiply(1.0 / sum2)

        return edges1, edges2

Pero por ejemplo en Dice aumenta el tiempo empleado de 8.314460 a 90.779462 ms, siendo bastante mayor o en Jaccard aumenta de 8.123826 a 88.041427ms por lo que no parece muy viable el cambio, quitando de lado todas las métricas que usan funciones de librería o necesitan normalización tenemos pocas un ejemplo puede ser Hamming o Cosine, métricas en las cuales también he visto que aumenta el tiempo al usar matrices dispersas, en Cosine pasamos de 28.322967 a 104.106299 ms.

Por lo cual interpreto que es mejor realizar operaciones con matrices densas para la parte de calculo de métricas puesto son mucho más eficientes y están mucho mejor optimizadas.

Un Saludo,

Ángel

simber72 commented 1 month ago

Hola Angel, ok!

Mirando el constructor de TEGGenerator:

    def __init__(self, observations_discretized, n_periods, n_bins):
        """
        Generates and set the time evolving graphs series from the discretized observations
        "observation_discretized" and the number of periods "n_periods"
        """
        n_obs = int(len(observations_discretized) / n_periods)  # number of observations per period
        self.__teg = []
        for period in range(n_periods):
            obs_discr_period = observations_discretized[period * n_obs:(period + 1) * n_obs]
            # Transforms to a dataframe (needed to generate the graph)
            df = pd.DataFrame({'Period': period * np.ones(n_obs), 'DP': obs_discr_period})
            gr = Graph(matrix= np.zeros((n_bins, n_bins), dtype=int)) #<--------------------------------- 
            gr.generate_graph(df)
            self.__teg.append(gr)

Veo que en la línea que crea un nuevo Grafo no se pasan los primeros dos argumentos (nodes, nodes_freq): se deberían pasar dos vectores de dimensión n_bins, como en la generación del grafo global (clase ModelBuilder):

    def __compute_global_graph(self, graphs):
        """
        Create and return a global graph as the sum of a list of "graphs".
        """
        n_bins = len(self.__le.get_levels())  
        self.__global_graph = Graph(np.arange(n_bins, dtype=int), np.zeros((n_bins), dtype=int), np.zeros((n_bins, n_bins), dtype=int))      

        for gr in graphs:
            self.__sum_graphs(self.__global_graph, gr)

Saludos, SImona

844759 commented 1 month ago

Hola Simona,

Gracias por decirmelo, lo he arreglado en el último commit que he realizado.

Un saludo, Ángel,