J'ai deux points en 3D:
(xa, ya, za)
(xb, yb, zb)
Et je veux calculer la distance:
dist = sqrt((xa-xb)^2 + (ya-yb)^2 + (za-zb)^2)
Quelle est la meilleure façon de faire cela avec NumPy ou avec Python en général? J'ai:
a = numpy.array((xa ,ya, za))
b = numpy.array((xb, yb, zb))
Utilisez numpy.linalg.norm
:
dist = numpy.linalg.norm(a-b)
Il y a une fonction pour cela dans SciPy. Ça s'appelle Euclidien .
Exemple:
from scipy.spatial import distance
a = (1, 2, 3)
b = (4, 5, 6)
dst = distance.euclidean(a, b)
Pour ceux qui s'intéressent au calcul de plusieurs distances à la fois, j’ai fait une petite comparaison avec perfplot (un de mes petits projets). Il se trouve que
a_min_b = a - b
numpy.sqrt(numpy.einsum('ij,ij->i', a_min_b, a_min_b))
calcule les distances des lignes dans a
et b
le plus rapide. Cela est vrai pour une seule rangée également!
Code pour reproduire l'intrigue:
import matplotlib
import numpy
import perfplot
from scipy.spatial import distance
def linalg_norm(data):
a, b = data
return numpy.linalg.norm(a-b, axis=1)
def sqrt_sum(data):
a, b = data
return numpy.sqrt(numpy.sum((a-b)**2, axis=1))
def scipy_distance(data):
a, b = data
return list(map(distance.euclidean, a, b))
def mpl_dist(data):
a, b = data
return list(map(matplotlib.mlab.dist, a, b))
def sqrt_einsum(data):
a, b = data
a_min_b = a - b
return numpy.sqrt(numpy.einsum('ij,ij->i', a_min_b, a_min_b))
perfplot.show(
setup=lambda n: numpy.random.Rand(2, n, 3),
n_range=[2**k for k in range(20)],
kernels=[linalg_norm, scipy_distance, mpl_dist, sqrt_sum, sqrt_einsum],
logx=True,
logy=True,
xlabel='len(x), len(y)'
)
Une autre instance de cette méthode de résolution de problème :
def dist(x,y):
return numpy.sqrt(numpy.sum((x-y)**2))
a = numpy.array((xa,ya,za))
b = numpy.array((xb,yb,zb))
dist_a_b = dist(a,b)
Je veux expliquer la réponse simple avec diverses notes de performance. np.linalg.norm fera peut-être plus que ce dont vous avez besoin:
dist = numpy.linalg.norm(a-b)
Premièrement, cette fonction est conçue pour traiter une liste et renvoyer toutes les valeurs, par exemple. comparer la distance de pA
à l'ensemble des points sP
:
sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.) # 'distances' is a list
Rappelez-vous plusieurs choses:
Alors
def distance(pointA, pointB):
dist = np.linalg.norm(pointA - pointB)
return dist
n'est pas aussi innocent qu'il y paraît.
>>> dis.dis(distance)
2 0 LOAD_GLOBAL 0 (np)
2 LOAD_ATTR 1 (linalg)
4 LOAD_ATTR 2 (norm)
6 LOAD_FAST 0 (pointA)
8 LOAD_FAST 1 (pointB)
10 BINARY_SUBTRACT
12 CALL_FUNCTION 1
14 STORE_FAST 2 (dist)
3 16 LOAD_FAST 2 (dist)
18 RETURN_VALUE
Tout d'abord, chaque fois que nous l'appelons, nous devons effectuer une recherche globale pour "np", une recherche étendue pour "linalg" et une recherche étendue pour "norm", et la surcharge de simplement appelant la fonction peut équivaloir à des dizaines d'instructions python.
Enfin, nous avons gaspillé deux opérations pour stocker le résultat et le recharger pour le retour ...
Premier passage à l'amélioration: accélérez la recherche, sautez le magasin
def distance(pointA, pointB, _norm=np.linalg.norm):
return _norm(pointA - pointB)
Nous obtenons le beaucoup plus simple:
>>> dis.dis(distance)
2 0 LOAD_FAST 2 (_norm)
2 LOAD_FAST 0 (pointA)
4 LOAD_FAST 1 (pointB)
6 BINARY_SUBTRACT
8 CALL_FUNCTION 1
10 RETURN_VALUE
Les frais généraux liés aux appels de fonctions représentent néanmoins encore du travail. Et vous voudrez faire des points de repère pour déterminer si vous feriez mieux de faire le calcul vous-même:
def distance(pointA, pointB):
return (
((pointA.x - pointB.x) ** 2) +
((pointA.y - pointB.y) ** 2) +
((pointA.z - pointB.z) ** 2)
) ** 0.5 # fast sqrt
Sur certaines plates-formes, **0.5
est plus rapide que math.sqrt
. Votre kilométrage peut varier.
**** Notes de performances avancées.
Pourquoi calculez-vous la distance? Si le seul but est de l'afficher,
print("The target is %.2fm away" % (distance(a, b)))
avancer. Mais si vous comparez les distances, effectuez des contrôles de distance, etc., j'aimerais ajouter quelques observations utiles sur les performances.
Prenons deux cas: trier par distance ou réduire une liste d’éléments répondant à une contrainte de plage.
# Ultra naive implementations. Hold onto your hat.
def sort_things_by_distance(Origin, things):
return things.sort(key=lambda thing: distance(Origin, thing))
def in_range(Origin, range, things):
things_in_range = []
for thing in things:
if distance(Origin, thing) <= range:
things_in_range.append(thing)
La première chose à retenir est que nous utilisons Pythagore pour calculer la distance (dist = sqrt(x^2 + y^2 + z^2)
), de sorte que nous passons de nombreux appels sqrt
. Math 101:
dist = root ( x^2 + y^2 + z^2 )
:.
dist^2 = x^2 + y^2 + z^2
and
sq(N) < sq(M) iff M > N
and
sq(N) > sq(M) iff N > M
and
sq(N) = sq(M) iff N == M
En bref: tant que nous n’aurons pas besoin de la distance dans une unité de X plutôt que de X ^ 2, nous pouvons éliminer la partie la plus difficile des calculs.
# Still naive, but much faster.
def distance_sq(left, right):
""" Returns the square of the distance between left and right. """
return (
((left.x - right.x) ** 2) +
((left.y - right.y) ** 2) +
((left.z - right.z) ** 2)
)
def sort_things_by_distance(Origin, things):
return things.sort(key=lambda thing: distance_sq(Origin, thing))
def in_range(Origin, range, things):
things_in_range = []
# Remember that sqrt(N)**2 == N, so if we square
# range, we don't need to root the distances.
range_sq = range**2
for thing in things:
if distance_sq(Origin, thing) <= range_sq:
things_in_range.append(thing)
Génial, les deux fonctions ne produisent plus de racines carrées coûteuses. Ce sera beaucoup plus rapide. Nous pouvons également améliorer in_range en le convertissant en générateur:
def in_range(Origin, range, things):
range_sq = range**2
yield from (thing for thing in things
if distance_sq(Origin, thing) <= range_sq)
Cela présente notamment des avantages si vous faites quelque chose comme:
if any(in_range(Origin, max_dist, things)):
...
Mais si la prochaine chose que vous allez faire nécessite une distance,
for nearby in in_range(Origin, walking_distance, hotdog_stands):
print("%s %.2fm" % (nearby.name, distance(Origin, nearby)))
envisager de donner des n-uplets:
def in_range_with_dist_sq(Origin, range, things):
range_sq = range**2
for thing in things:
dist_sq = distance_sq(Origin, thing)
if dist_sq <= range_sq: yield (thing, dist_sq)
Cela peut être particulièrement utile si vous pouvez enchaîner les vérifications de plage ('rechercher des objets proches de X et situés dans les limites de Nm de Y', car vous n'avez pas à calculer la distance à nouveau).
Mais qu’en est-il si nous recherchons une très grande liste de things
et si nous nous attendons à ce qu’un grand nombre d’entre elles ne soient pas considérées?
Il y a en fait une optimisation très simple:
def in_range_all_the_things(Origin, range, things):
range_sq = range**2
for thing in things:
dist_sq = (Origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (Origin.y - thing.y) ** 2
if dist_sq <= range_sq:
dist_sq += (Origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
Si cela est utile dépendra de la taille des «choses».
def in_range_all_the_things(Origin, range, things):
range_sq = range**2
if len(things) >= 4096:
for thing in things:
dist_sq = (Origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (Origin.y - thing.y) ** 2
if dist_sq <= range_sq:
dist_sq += (Origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
Elif len(things) > 32:
for things in things:
dist_sq = (Origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (Origin.y - thing.y) ** 2 + (Origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
else:
... just calculate distance and range-check it ...
Et encore une fois, envisagez de céder le dist_sq. Notre exemple de hot-dog devient alors:
# Chaining generators
info = in_range_with_dist_sq(Origin, walking_distance, hotdog_stands)
info = (stand, dist_sq**0.5 for stand, dist_sq in info)
for stand, dist in info:
print("%s %.2fm" % (stand, dist))
Je trouve une fonction 'dist' dans matplotlib.mlab, mais je ne pense pas que ce soit assez pratique.
Je poste ici juste pour référence.
import numpy as np
import matplotlib as plt
a = np.array([1, 2, 3])
b = np.array([2, 3, 4])
# Distance between a and b
dis = plt.mlab.dist(a, b)
Cela peut être fait comme suit. Je ne sais pas à quelle vitesse, mais il n’utilise pas NumPy.
from math import sqrt
a = (1, 2, 3) # Data point 1
b = (4, 5, 6) # Data point 2
print sqrt(sum( (a - b)**2 for a, b in Zip(a, b)))
Un one-liner sympa:
dist = numpy.linalg.norm(a-b)
Cependant, si la vitesse est un problème, je vous recommande de faire des essais sur votre machine. J'ai constaté que l'utilisation de la variable math
de la bibliothèque sqrt
avec l'opérateur **
pour le carré est beaucoup plus rapide sur ma machine que la solution à une couche NumPy.
J'ai exécuté mes tests en utilisant ce programme simple:
#!/usr/bin/python
import math
import numpy
from random import uniform
def fastest_calc_dist(p1,p2):
return math.sqrt((p2[0] - p1[0]) ** 2 +
(p2[1] - p1[1]) ** 2 +
(p2[2] - p1[2]) ** 2)
def math_calc_dist(p1,p2):
return math.sqrt(math.pow((p2[0] - p1[0]), 2) +
math.pow((p2[1] - p1[1]), 2) +
math.pow((p2[2] - p1[2]), 2))
def numpy_calc_dist(p1,p2):
return numpy.linalg.norm(numpy.array(p1)-numpy.array(p2))
TOTAL_LOCATIONS = 1000
p1 = dict()
p2 = dict()
for i in range(0, TOTAL_LOCATIONS):
p1[i] = (uniform(0,1000),uniform(0,1000),uniform(0,1000))
p2[i] = (uniform(0,1000),uniform(0,1000),uniform(0,1000))
total_dist = 0
for i in range(0, TOTAL_LOCATIONS):
for j in range(0, TOTAL_LOCATIONS):
dist = fastest_calc_dist(p1[i], p2[j]) #change this line for testing
total_dist += dist
print total_dist
Sur ma machine, math_calc_dist
est beaucoup plus rapide que numpy_calc_dist
: 1,5 seconde versus 23,5 secondes .
Pour obtenir une différence mesurable entre fastest_calc_dist
et math_calc_dist
, je devais monter TOTAL_LOCATIONS
jusqu'à 6000. Alors fastest_calc_dist
prend ~ 50 secondes tandis que math_calc_dist
prend ~ 60 secondes .
Vous pouvez également expérimenter avec numpy.sqrt
et numpy.square
bien que les deux aient été plus lents que les alternatives math
sur ma machine.
Mes tests ont été exécutés avec Python 2.6.6.
Vous pouvez simplement soustraire les vecteurs et ensuite le produit interne.
En suivant votre exemple,
a = numpy.array((xa, ya, za))
b = numpy.array((xb, yb, zb))
tmp = a - b
sum_squared = numpy.dot(tmp.T, tmp)
result sqrt(sum_squared)
C'est un code simple et facile à comprendre.
Ayant a
et b
tels que vous les avez définis, vous pouvez également utiliser:
distance = np.sqrt(np.sum((a-b)**2))
À partir de Python 3.8
, le module math
fournit directement la fonction dist
, qui renvoie la distance euclidienne entre deux points (exprimée en tuple de coordonnées):
from python import math
dist((1, 2, 6), (-2, 3, 2)) # 5.0990195135927845
Si vous travaillez avec des listes au lieu de tuples:
dist(Tuple([1, 2, 6]), Tuple([-2, 3, 2]))
J'aime np.dot
(produit scalaire):
a = numpy.array((xa,ya,za))
b = numpy.array((xb,yb,zb))
distance = (np.dot(a-b,a-b))**.5
Voici un code concis pour la distance euclidienne en Python à partir de deux points représentés sous forme de listes en Python.
def distance(v1,v2):
return sum([(x-y)**2 for (x,y) in Zip(v1,v2)])**(0.5)
Vous pouvez facilement utiliser la formule
distance = np.sqrt(np.sum(np.square(a-b)))
ce qui ne fait en réalité rien d'autre que d'utiliser le théorème de Pythagore pour calculer la distance, en ajoutant les carrés de Δx, Δy et Δz et en enracinant le résultat.
Calculez la distance euclidienne pour un espace multidimensionnel:
import math
x = [1, 2, 6]
y = [-2, 3, 2]
dist = math.sqrt(sum([(xi-yi)**2 for xi,yi in Zip(x, y)]))
5.0990195135927845
import numpy as np
from scipy.spatial import distance
input_arr = np.array([[0,3,0],[2,0,0],[0,1,3],[0,1,2],[-1,0,1],[1,1,1]])
test_case = np.array([0,0,0])
dst=[]
for i in range(0,6):
temp = distance.euclidean(test_case,input_arr[i])
dst.append(temp)
print(dst)
import math
dist = math.hypot(math.hypot(xa-xb, ya-yb), za-zb)
Trouve la différence de deux matrices en premier. Appliquez ensuite la multiplication élément par élément avec la commande multiply de numpy. Ensuite, trouvez la somme de l’élément multiplié par la nouvelle matrice. Enfin, trouvez la racine carrée de la somme.
def findEuclideanDistance(a, b):
euclidean_distance = a - b
euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
euclidean_distance = np.sqrt(euclidean_distance)
return euclidean_distance