Neulich stieß ich auf eine Bibliothek, von der ich noch nie zuvor gehört hatte. Es wurde genannt Numexpr.
Ich warfare sofort interessiert wegen einiger Behauptungen über die Bibliothek. Insbesondere wurde festgestellt, dass es für einige komplexe numerische Berechnungen bis zu 15 -mal schneller warfare als Numpy.
Ich warfare fasziniert, weil Numpy bisher in seiner Dominanz im numerischen Berechnungsraum in Python unangefochten geblieben ist. Insbesondere mit DatenwissenschaftNumpy ist ein Eckpfeiler für maschinelles Lernen, explorative Datenanalyse und Modelltraining. Alles, was wir verwenden können, um jede letzte Leistung in unseren Systemen auszudrücken, wird begrüßt. Additionally beschloss ich, die Behauptungen selbst auf den Take a look at zu setzen.
Am Ende dieses Artikels finden Sie einen Hyperlink zum NumexPR -Repository.
Was ist numexpr?
Nach seiner GitHub -Seite ist numexpr ein schneller numerischer Expressions -Bewerter für Numpy. Mit der Verwendung werden Ausdrücke, die auf Arrays arbeiten, beschleunigt und verwenden weniger Speicher als die gleichen Berechnungen in Python mit anderen numerischen Bibliotheken wie Numph.
Darüber hinaus kann numexpr, da es multitHehead ist, alle Ihre CPU -Kerne verwenden, was im Allgemeinen zu einer erheblichen Leistungsskalierung im Vergleich zu Numpy führt.
Einrichtung einer Entwicklungsumgebung
Bevor wir mit dem Codieren beginnen, richten wir unsere Entwicklungsumgebung ein. Die beste Praxis ist es, eine separate zu erstellen Python Umgebung, in der Sie alle erforderlichen Software program installieren und mit Codierung experimentieren können, wusste ich, dass alles, was Sie in dieser Umgebung tun, den Relaxation Ihres Programs nicht beeinflusst. Ich benutze Conda dafür, aber Sie können jede Methode verwenden, die Sie am besten wissen, die zu Ihnen passt.
Wenn Sie die Miniconda -Route hinuntergehen möchten und sie noch nicht haben, müssen Sie zuerst Miniconda installieren. Holen Sie es mit diesem Hyperlink:
https://www.anaconda.com/docs/principal
1/ Erstellen Sie unsere neue Entwicklerumgebung und installieren Sie die erforderlichen Bibliotheken
(base) $ conda create -n numexpr_test python=3.12-y
(base) $ conda activate numexpr
(numexpr_test) $ pip set up numexpr
(numexpr_test) $ pip set up jupyter
2/ Begin Jupyter
Tippen Sie jetzt ein jupyter pocket book in Ihre Eingabeaufforderung. Sie sollten ein Jupyter -Notizbuch in Ihrem Browser geöffnet sehen. Wenn das nicht automatisch passiert, werden Sie wahrscheinlich eine Bildschirminformationen nach dem feststellen jupyter pocket book Befehl. Nahe unten finden Sie eine URL, die Sie kopieren und in Ihren Browser einfügen sollten, um das Jupyter -Notizbuch zu starten.
Ihre URL wird anders sein als meine, aber sie sollte ungefähr so aussehen:-
http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69
Vergleich von Numexpr und Numpy Efficiency
Um die Leistung zu vergleichen, werden wir eine Reihe von numerischen Berechnungen mit Numpy und Numexpr und beiden Systemen ausführen.
Beispiel 1 – Eine einfache Array -Additionsberechnung
In diesem Beispiel führen wir eine vektorisierte Zugabe von zwei großen Arrays 5000 -mal durch.
import numpy as np
import numexpr as ne
import timeit
a = np.random.rand(1000000)
b = np.random.rand(1000000)
# Utilizing timeit with lambda capabilities
time_np_expr = timeit.timeit(lambda: 2*a + 3*b, quantity=5000)
time_ne_expr = timeit.timeit(lambda: ne.consider("2*a + 3*b"), quantity=5000)
print(f"Execution time (NumPy): {time_np_expr} seconds")
print(f"Execution time (NumExpr): {time_ne_expr} seconds")
>>>>>>>>>>>
Execution time (NumPy): 12.03680682599952 seconds
Execution time (NumExpr): 1.8075962659931974 seconds
Ich muss sagen, das ist bereits ein beeindruckender Begin in der Numexpr -Bibliothek. Ich mache das eine 6 -fache Verbesserung gegenüber der Numpy -Laufzeit.
Überprüfen Sie, ob beide Vorgänge das gleiche Ergebnissatz zurückgeben.
# Arrays to retailer the outcomes
result_np = 2*a + 3*b
result_ne = ne.consider("2*a + 3*b")
# Guarantee the 2 new arrays are equal
arrays_equal = np.array_equal(result_np, result_ne)
print(f"Arrays equal: {arrays_equal}")
>>>>>>>>>>>>
Arrays equal: True
Beispiel 2 – Berechnen Sie PI unter Verwendung einer Monte -Carlo -Simulation
Unser zweites Beispiel untersucht einen komplizierteren Anwendungsfall mit mehr realen Anwendungen.
Monte -Carlo -Simulationen umfassen viele Iterationen eines zufälligen Prozesses, um die Eigenschaften eines Programs abzuschätzen, was rechnerisch intensiv sein kann.
In diesem Fall werden wir Monte Carlo verwenden, um den Wert von PI zu berechnen. Dies ist ein bekanntes Beispiel, bei dem wir ein Quadrat mit einer Seitenlänge einer Einheit einnehmen und einen Viertelkreis mit einem Radius einer Einheit einschreiben. Das Verhältnis des Viertelkreises zum Platz des Quadrats ist (π/4)/1, und wir können diesen Ausdruck mit vier multiplizieren, um sie zu bekommen π von allein.
Wenn wir additionally zahlreiche zufällige (x, y) Punkte betrachten, die alle innerhalb oder an den Grenzen des Quadrats liegen, da die Gesamtzahl dieser Punkte tendenziell unendlich ist, ist das Verhältnis der Punkte, die auf oder innerhalb des Viertelkreises liegen, zur Gesamtzahl der Punkte in Richtung PI.
Erstens die Numpy -Implementierung.
import numpy as np
import timeit
def monte_carlo_pi_numpy(num_samples):
x = np.random.rand(num_samples)
y = np.random.rand(num_samples)
inside_circle = (x**2 + y**2) <= 1.0
pi_estimate = (np.sum(inside_circle) / num_samples) * 4
return pi_estimate
# Benchmark the NumPy model
num_samples = 1000000
time_np_expr = timeit.timeit(lambda: monte_carlo_pi_numpy(num_samples), quantity=1000)
pi_estimate = monte_carlo_pi_numpy(num_samples)
print(f"Estimated Pi (NumPy): {pi_estimate}")
print(f"Execution Time (NumPy): {time_np_expr} seconds")
>>>>>>>>
Estimated Pi (NumPy): 3.144832
Execution Time (NumPy): 10.642843848007033 seconds
Nun mit numexpr.
import numpy as np
import numexpr as ne
import timeit
def monte_carlo_pi_numexpr(num_samples):
x = np.random.rand(num_samples)
y = np.random.rand(num_samples)
inside_circle = ne.consider("(x**2 + y**2) <= 1.0")
pi_estimate = (np.sum(inside_circle) / num_samples) * 4 # Use NumPy for summation
return pi_estimate
# Benchmark the NumExpr model
num_samples = 1000000
time_ne_expr = timeit.timeit(lambda: monte_carlo_pi_numexpr(num_samples), quantity=1000)
pi_estimate = monte_carlo_pi_numexpr(num_samples)
print(f"Estimated Pi (NumExpr): {pi_estimate}")
print(f"Execution Time (NumExpr): {time_ne_expr} seconds")
>>>>>>>>>>>>>>>
Estimated Pi (NumExpr): 3.141684
Execution Time (NumExpr): 8.077501275009126 seconds
Okay, die Beschleunigung warfare zu dieser Zeit nicht so beeindruckend, aber eine Verbesserung von 20% ist auch nicht schrecklich. Ein Teil des Grundes ist, dass numexpr keine optimierte SUM () -Funktion hat, daher mussten wir für diese Operation standardmäßig auf Numpy zurückkehren.
Beispiel 3 – Implementierung eines Sobel -Bildfilters
In diesem Beispiel werden wir einen Sobelfilter für Bilder implementieren. Der Sobelfilter wird üblicherweise bei der Bildverarbeitung zur Kantenerkennung verwendet. Es berechnet den Bildintensitätsgradienten an jedem Pixel, wobei Kanten und Intensitätsübergänge hervorgehoben werden. Unser Eingabebild ist der Taj Mahal in Indien.

Lassen Sie uns den Numpy -Code sehen, der zuerst und Zeit ausgeführt wird.
import numpy as np
from scipy.ndimage import convolve
from PIL import Picture
import timeit
# Sobel kernels
sobel_x = np.array(((-1, 0, 1),
(-2, 0, 2),
(-1, 0, 1)))
sobel_y = np.array(((-1, -2, -1),
( 0, 0, 0),
( 1, 2, 1)))
def sobel_filter_numpy(picture):
"""Apply Sobel filter utilizing NumPy."""
img_array = np.array(picture.convert('L')) # Convert to grayscale
gradient_x = convolve(img_array, sobel_x)
gradient_y = convolve(img_array, sobel_y)
gradient_magnitude = np.sqrt(gradient_x**2 + gradient_y**2)
gradient_magnitude *= 255.0 / gradient_magnitude.max() # Normalize to 0-255
return Picture.fromarray(gradient_magnitude.astype(np.uint8))
# Load an instance picture
picture = Picture.open("/mnt/d/take a look at/taj_mahal.png")
# Benchmark the NumPy model
time_np_sobel = timeit.timeit(lambda: sobel_filter_numpy(picture), quantity=100)
sobel_image_np = sobel_filter_numpy(picture)
sobel_image_np.save("/mnt/d/take a look at/sobel_taj_mahal_numpy.png")
print(f"Execution Time (NumPy): {time_np_sobel} seconds")
>>>>>>>>>
Execution Time (NumPy): 8.093792188999942 seconds
Und jetzt der Numexpr -Code.
import numpy as np
import numexpr as ne
from scipy.ndimage import convolve
from PIL import Picture
import timeit
# Sobel kernels
sobel_x = np.array(((-1, 0, 1),
(-2, 0, 2),
(-1, 0, 1)))
sobel_y = np.array(((-1, -2, -1),
( 0, 0, 0),
( 1, 2, 1)))
def sobel_filter_numexpr(picture):
"""Apply Sobel filter utilizing NumExpr for gradient magnitude computation."""
img_array = np.array(picture.convert('L')) # Convert to grayscale
gradient_x = convolve(img_array, sobel_x)
gradient_y = convolve(img_array, sobel_y)
gradient_magnitude = ne.consider("sqrt(gradient_x**2 + gradient_y**2)")
gradient_magnitude *= 255.0 / gradient_magnitude.max() # Normalize to 0-255
return Picture.fromarray(gradient_magnitude.astype(np.uint8))
# Load an instance picture
picture = Picture.open("/mnt/d/take a look at/taj_mahal.png")
# Benchmark the NumExpr model
time_ne_sobel = timeit.timeit(lambda: sobel_filter_numexpr(picture), quantity=100)
sobel_image_ne = sobel_filter_numexpr(picture)
sobel_image_ne.save("/mnt/d/take a look at/sobel_taj_mahal_numexpr.png")
print(f"Execution Time (NumExpr): {time_ne_sobel} seconds")
>>>>>>>>>>>>>
Execution Time (NumExpr): 4.938702256011311 seconds
Bei dieser Gelegenheit führte die Verwendung von numexPR zu einem großartigen Ergebnis mit einer Leistung, die quick doppelt so hoch warfare wie Numpy.
So sieht das kandidierten Bild aus.

Beispiel 4 – Annäherung der Fourier -Serie
Es ist bekannt, dass komplexe periodische Funktionen simuliert werden können, indem eine Reihe von Sinuswellen aufeinander angewendet werden. Im Extremwert kann sogar eine Quadratwelle auf diese Weise leicht modelliert werden. Die Methode wird als Fourier -Serie -Näherung bezeichnet. Obwohl eine Annäherung, können wir die Zielwellenform so nah wie Speicher und Rechenkapazität zulässt.
Die Mathematik hinter all dem ist nicht der Hauptaugenmerk. Beachten Sie nur, dass die Laufzeit der Lösung, wenn wir die Anzahl der Iterationen erhöhen, deutlich steigt.
import numpy as np
import numexpr as ne
import time
import matplotlib.pyplot as plt
# Outline the fixed pi explicitly
pi = np.pi
# Generate a time vector and a sq. wave sign
t = np.linspace(0, 1, 1000000) # Decreased dimension for higher visualization
sign = np.signal(np.sin(2 * np.pi * 5 * t))
# Variety of phrases within the Fourier sequence
n_terms = 10000
# Fourier sequence approximation utilizing NumPy
start_time = time.time()
approx_np = np.zeros_like
for n in vary(1, n_terms + 1, 2):
approx_np += (4 / (np.pi * n)) * np.sin(2 * np.pi * n * 5 * t)
numpy_time = time.time() - start_time
# Fourier sequence approximation utilizing NumExpr
start_time = time.time()
approx_ne = np.zeros_like
for n in vary(1, n_terms + 1, 2):
approx_ne = ne.consider("approx_ne + (4 / (pi * n)) * sin(2 * pi * n * 5 * t)", local_dict={"pi": pi, "n": n, "approx_ne": approx_ne, "t": t})
numexpr_time = time.time() - start_time
print(f"NumPy Fourier sequence time: {numpy_time:.6f} seconds")
print(f"NumExpr Fourier sequence time: {numexpr_time:.6f} seconds")
# Plotting the outcomes
plt.determine(figsize=(10, 6))
plt.plot(t, sign, label='Unique Sign (Sq. Wave)', shade='black', linestyle='--')
plt.plot(t, approx_np, label='Fourier Approximation (NumPy)', shade='blue')
plt.plot(t, approx_ne, label='Fourier Approximation (NumExpr)', shade='pink', linestyle='dotted')
plt.title('Fourier Collection Approximation of a Sq. Wave')
plt.xlabel('Time')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.present()
Und die Ausgabe?

Das ist ein weiteres ziemlich gutes Ergebnis. Numexpr zeigt bei dieser Gelegenheit eine 5 -fache Verbesserung gegenüber Numpy.
Zusammenfassung
Numpy und numexpr sind beide leistungsstarke Bibliotheken, die für Python Numerical Berechnungen verwendet werden. Sie haben jeweils einzigartige Stärken und Anwendungsfälle, wodurch sie für verschiedene Arten von Aufgaben geeignet sind. Hier haben wir ihre Leistung und Eignung für bestimmte Rechenaufgaben verglichen und uns auf Beispiele wie einfache Array -Ergänzung zu komplexeren Anwendungen konzentrieren, z. B. die Verwendung eines Sobelfilters zur Erkennung von Bildkanten.
Obwohl ich in meinen Checks nicht genau gesehen habe, dass die um 15 -fach -Geschwindigkeit über Numpy zunahm, besteht kein Zweifel daran, dass Numexprin in vielen Fällen erheblich schneller sein kann als Numpy.
Wenn Sie ein starker Benutzer von Numpy sind und alle Leistung aus Ihrem Code extrahieren müssen, empfehle ich, die NumexPR -Bibliothek auszuprobieren. Neben der Tatsache, dass nicht alle Numpy -Code mit numexPR repliziert werden können, gibt es praktisch keinen Nachteil, und der Vorteil könnte Sie überraschen.
Weitere Informationen in der NumexPR -Bibliothek finden Sie auf der GitHub -Seite Hier.
