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 :
résultat:
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)
ceci correspond bien à ce que je souhaite faire et les résultats numériques obtenus pour c semblent bien convenir aux résultats attendus.cas 1: (1, 4)
cas 1b: (2, 4)
cas 2: (1, 4)
cas 2: (3, 4)
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 :
Un grand merci par avance pour tout indice ou solution que vous pourriez m'apporter afin d'optimiser un peu cette 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
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)
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 !
Partager