IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Numpy: optimisation sur des opérations tensorielles


Sujet :

Python

  1. #1
    Membre éclairé Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Par défaut Numpy: optimisation sur des opérations tensorielles
    Bonjour à vous,

    Je travaille présentement sur une fonction réalisant de simples calculs tensoriels, avec numpy 1.20.2 sous python 3.9.0.
    Cette dernière prends donc 2 np.array (de dimensions spécifiques) en entré et doit retourner un np.array résultant des deux premiers.
    voici un code représentant la-dite fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
     
    import numpy as np  
     
     
    def process(a:np.array, b:np.array)->np.array:
        """
        Dans l'utilisation de cette fonction, trois situations sont possibles.
        Pour chaque cas, i et j > 0, a.ndim == 2 et b.ndim == 2 ou b.ndim == 3
        cas1: a.shape == b.shape == (i,j) soit ij,ij->ij
            dans ce cas, on souhaite juste une multiplication terme à terme
            des entrées, tel que c = a * b et donc c.shape == (i,j)
        
        cas 2: a.shape == (1,j) et b.shape == (j,j) soit 1j,jj->1j
            dans cette situation, c'est donc un dot product qui est attendu et
            a.shape[0] == 1 (pas de cas où a.shape[0] > 1 dans cette situation)
            c = a @ b ou encore c = np.dot(a,b) ; là encore, c.shape == (1,j)
        
        cas 3: a.shape == (i,j) et b.shape == (i,j,j), retour attendu: ij,ijj->ij
            pour cette situation le processus est particulier car le tenseur b est
            à traiter comme un empilement de matrices dans le sens où:
                pour tout "i":
                    c[i] = a[i] @ b[i]
            donc pas de produit attendu sur la troisième dimension
            
        """
     
        # cas 1: a.shape == b.shape == (i,j)
        if a.shape == b.shape and a.ndim == b.ndim == 2:
            return a * b # j'attends bien ici une multiplication terme à terme
     
        # cas 2: a.shape == (1,j) et b.shape == (j,j)
        elif a.shape[1] == b.shape[0] and b.ndim == 2 and a.shape[0] == 1:
            return a @ b # ou encore return np.dot(a,b)
     
        # cas 3: a.shape == (i,j) et b.shape == (i,j,j)
        elif a.shape[0] == b.shape[0] and b.ndim == 3:
            # ici, la meilleure solution que j'ai trouvé:
            buff = np.zeros(shape=(a.shape))
            for i in range(a.shape[0]):
                buff[i] = np.dot(a[i], b[i])
            return buff
     
        # erreur si on rencontre un autre cas, ce qui ne devrais pas arriver
        else:
            raise ValueError("dimensions de a et/ou b non reconnues")
     
     
     
    if __name__ == "__main__":
     
        # cas 1:
        a = np.random.randint(0,5, size=(1,4))
        b = np.random.randint(0,5, size=(1,4))
        c = process(a,b)
        print("cas 1: ", c.shape)
     
        # cas 1 bis:
        a = np.random.randint(0,5, size=(2,4))
        b = np.random.randint(0,5, size=(2,4))
        c = process(a,b)
        print("cas 1b: ", c.shape)
     
        # cas 2:
        a = a = np.random.randint(0,5, size=(1,4))
        b = b = np.random.randint(0,5, size=(4,4))
        c = process(a,b)
        print("cas 2: ", c.shape)
     
        # cas 3:
        a = np.random.randint(0,5, size=(3,4))
        b = np.random.randint(0,5, size=(3,4,4))
        c = process(a,b)
        print("cas 3: ", c.shape)
    résultat:
    cas 1: (1, 4)
    cas 1b: (2, 4)
    cas 2: (1, 4)
    cas 2: (3, 4)
    ceci correspond bien à ce que je souhaite faire et les résultats numériques obtenus pour c semblent bien convenir aux résultats attendus.

    Je pense que tout le monde comprends rapidement le problème: il ne me semble pas possible que le cas 1 et 2 puissent être traités avec une seule et même fonction compte tenu de la nature des calculs attendus; Cependant, peut-être que ceci existe tout de même auquel-cas, je suis preneur de la solution.
    Pour le cas 3 en revanche, je suis absolument persuadé que numpy propose une solution bien plus performante que ma boucle sur la première dimension et, peut-être même que la fonction employée pour le cas 2 et 3 pourrait être la même.
    Ainsi, sauriez-vous m’orienter vers une solution plus générale, plus élégante, mais surtout plus performante que celle avec laquelle je suis arrivé ?

    Afin de limiter éventuellement des questions subsidiaires, sachez que j'ai déjà fait quelques recherches en vain sur le sujet dont voici les résultats :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
     
    def process_bis(a:np.array, b:np.array)->np.array:
        """
        ici, l'objectif est de trouver une solution vectorisée pour le cas 3 vu
        précédement, afin de rendre le code plus lisible et surtout plus performant.
        Pour rappel, on souhaite que ij,ijj->ij
        J'ai pu tester les trois solutions suivantes, sans succes.
        avec, pour exemple: 
        a = [[4,1,2,4],
             [1,2,4,0]]
        b = [[[0,0,3,0],
              [0,1,1,4],
              [2,3,0,0],
              [3,0,4,0]],
             
             [[2,3,2,4],
              [0,3,3,1],
              [0,2,1,2],
              [1,2,1,1]]]
        la valeur attendue est :
        c = [[16,7,23,4],
             [2,17,12,14]]
        """
     
        # solution A:
        sol_a = np.dot(a,b)
        # retour : sol_a.shape == (i,i,j) != (i,j,j)
        # sol_a = [[[16  7 29  4],
        #           [12 27 17 25]],
     
        #          [[ 8 14  5  8],
        #           [ 2 17 12 14]]]
     
     
        # solution B:
        sol_b = np.matmul(a,b)
        # même comportement que ci-dessus
        # retour : sol_b.shape == (i,i,j) != (i,j,j)
        # sol_b = [[[16  7 29  4], 
        #           [ 8 14  5  8]],
     
        #          [[12 27 17 25],
        #           [ 2 17 12 14]]]
     
     
        # solution C:
        sol_c = np.einsum("ij,ijj->ij",a,b)
        # les dimensions sont bonnes mais pas les valeurs 
        # c = [[0 1 0 0],
        #      [2 6 4 0]]
     
     
     
        return sol_a, sol_b, sol_c
     
     
    if __name__ == "__main__":
        # process_bis:
        a = np.array([[4,1,2,4],
             [1,2,4,0]])
        b = np.array([[[0,0,3,0],
              [0,1,1,4],
              [2,3,0,0],
              [3,0,4,0]],
     
             [[2,3,2,4],
              [0,3,3,1],
              [0,2,1,2],
              [1,2,1,1]]])
     
        c = process(a,b)
        sol_a, sol_b, sol_c = process_bis(a,b)
        print("valeur attendue: ", c)
        print("sol A: ", sol_a)
        print("sol B: ", sol_b)
        print("sol C: ", sol_c)
    Un grand merci par avance pour tout indice ou solution que vous pourriez m'apporter afin d'optimiser un peu cette fonction.
    Pour info, cette dernière intervient dans une boucle assez costaud dans mon programme, et les dimensions des vecteurs / matrices en jeux peuvent être relativement grandes (entre 2 et plusieurs milliers de valeurs).

    Merci et bonne journée !

  2. #2
    Membre éclairé Avatar de BioKore
    Homme Profil pro
    Dresseur d'Alpaga
    Inscrit en
    Septembre 2016
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Dresseur d'Alpaga

    Informations forums :
    Inscription : Septembre 2016
    Messages : 300
    Par défaut
    Après m'être un peu plus penché sur la méthode via la convention d'Einstein, j'ai pu trouvé une solution pour le cas 3: np.einsum("ik,ikj->ij",a,b). C'est bien moi qui utilisais cette fonction comme un pied sans plus me pencher sur le sujet.
    Il me reste donc à confirmer si je peux retirer une condition ou non et m'assurer que cette méthode est bien plus performante que la précédente.

Discussions similaires

  1. Réponses: 8
    Dernier message: 17/03/2017, 09h48
  2. Optimisation des opérations sur les grands nombres, algorithme de Knuth
    Par Jackyzgood dans le forum Algorithmes et structures de données
    Réponses: 8
    Dernier message: 21/10/2010, 20h27
  3. optimisation sur des listes
    Par cesium dans le forum Algorithmes et structures de données
    Réponses: 2
    Dernier message: 13/07/2010, 19h13
  4. Réponses: 9
    Dernier message: 17/06/2009, 15h10
  5. Opération sur des heures dans Excel
    Par mirascheat dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 15/12/2005, 10h34

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo