Wróć do katalogu
Season 34 7 Odcinki 26 min 2026

NumPy

v2.4 — Edycja 2026. Kurs audio wprowadzający do NumPy, wyjaśniający jego wysoką wydajność, wielowymiarowe tablice oraz kluczową rolę w ekosystemie Python. (v2.4, Edycja 2026)

Nauka o danych Python Core
NumPy
Teraz odtwarzane
Click play to start
0:00
0:00
1
Podstawowa tożsamość: ndarray
Ten odcinek omawia obiekt ndarray, jednorodne typy danych i stałą alokację pamięci. Dowiesz się, dlaczego standardowe listy w języku Python są nieefektywne w obliczeniach matematycznych na dużą skalę i jak NumPy rozwiązuje ten problem, schodząc do poziomu skompilowanego kodu C.
3m 35s
2
Przywoływanie tablic: Tworzenie i kształt
Ten odcinek bada, jak prawidłowo tworzyć wielowymiarowe tablice za pomocą wbudowanych funkcji. Dowiesz się, jak używać narzędzi takich jak zeros, arange i linspace, aby błyskawicznie generować zbiory danych.
4m 26s
3
Pod maską: Pamięć, strides i widoki
Ten odcinek zagłębia się w wewnętrzną architekturę NumPy, skupiając się na buforze danych i strides. Dowiesz się, dlaczego operacje takie jak slicing i transponowanie są niemal natychmiastowe, ponieważ zwracają widoki pamięci, a nie kopie.
3m 47s
4
Funkcje uniwersalne: Matematyka bez pętli
Ten odcinek omawia funkcje uniwersalne (ufuncs) i sposób, w jaki wektoryzują one operacje. Dowiesz się, jak całkowicie wyeliminować pętle for w języku Python, stosując matematykę element po elemencie oraz redukcje oparte na osiach.
3m 29s
5
Broadcasting: Magia niedopasowanych kształtów
Ten odcinek wyjaśnia dokładne zasady mechanizmu Broadcasting. Dowiesz się, jak NumPy koncepcyjnie rozciąga tablice o niedopasowanych kształtach, aby mogły być przetwarzane razem bez marnowania pamięci.
3m 48s
6
Precyzyjne filtrowanie: Maskowanie logiczne
Ten odcinek skupia się na zaawansowanym maskowaniu logicznym do filtrowania złożonych zbiorów danych. Dowiesz się, jak wyodrębniać bardzo specyficzne punkty danych z ogromnych tablic za pomocą prostej logiki warunkowej.
3m 41s
7
Uniwersalny tłumacz: Interoperacyjność
Ten odcinek ujawnia, dlaczego NumPy pozostaje kręgosłupem analizy danych w języku Python. Dowiesz się, jak DLPack i array interface pozwalają na współdzielenie pamięci zero-copy między narzędziami takimi jak Pandas i PyTorch.
4m 11s

Odcinki

1

Podstawowa tożsamość: ndarray

3m 35s

Ten odcinek omawia obiekt ndarray, jednorodne typy danych i stałą alokację pamięci. Dowiesz się, dlaczego standardowe listy w języku Python są nieefektywne w obliczeniach matematycznych na dużą skalę i jak NumPy rozwiązuje ten problem, schodząc do poziomu skompilowanego kodu C.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 1 z 7. Standardowa lista w Pythonie jest bardzo elastyczna, ale w momencie, gdy próbujesz wykonać operacje matematyczne na milionie elementów, uderzasz w ścianę wydajności. W efekcie płacisz ogromny podatek od wskaźników tylko po to, żeby pomnożyć przez siebie liczby. Rozwiązaniem tego wąskiego gardła jest główny silnik obliczeń naukowych w Pythonie: ndarray. Aby zrozumieć, dlaczego ndarray w ogóle istnieje, musisz zajrzeć pod maskę i zobaczyć, co robią standardowe listy. Lista w Pythonie nie przechowuje surowych liczb. Przechowuje wskaźniki. Każdy wskaźnik kieruje system do jakiegoś rozproszonego miejsca w pamięci, gdzie żyje pełnoprawny obiekt Pythona. Jeśli napiszesz standardową pętlę, żeby pomnożyć dwie sekwencje miliona liczb, interpreter Pythona musi się strasznie napracować. Dla każdego pojedynczego elementu pobiera wskaźnik, lokalizuje obiekt, sprawdza jego typ danych, żeby upewnić się, że to faktycznie liczba, wykonuje obliczenia i zapisuje nowy obiekt. Zrobienie tego milion razy wprowadza ogromny overhead. Ten cykl skakania po wskaźnikach i sprawdzania typów to powód, dla którego standardowe pętle są po prostu zbyt wolne do dużych operacji matematycznych. Obiekt ndarray, co oznacza N-wymiarową tablicę, porzuca tę elastyczność w zamian za czystą prędkość. Człon N-wymiarowa oznacza, że ten obiekt może reprezentować płaską sekwencję liczb, dwuwymiarową siatkę albo złożoną, wielowymiarową macierz matematyczną. Niezależnie od tego, ile wymiarów zdefiniujesz, pod maską działa on w oparciu o dwie surowe zasady. Po pierwsze, wymaga jednorodnych typów danych. Każdy pojedynczy element w ndarray musi być dokładnie tego samego typu, na przykład 64-bitowym floatem. Po drugie, używa stałego rozmiaru pamięci. Kiedy tworzysz ndarray, NumPy rezerwuje pojedynczy, ciągły blok pamięci. Nie ma tu żadnych wskaźników. Surowe liczby siedzą ciasno upakowane obok siebie w pamięci systemowej. Oto kluczowa kwestia. Ponieważ NumPy zna dokładny typ danych i dokładny układ pamięci, może całkowicie pominąć powolny interpreter Pythona. Kiedy mnożysz dwa ndarray zawierające milion liczb, nie piszesz pętli. Po prostu piszesz tablica A razy tablica B. Ten proces nazywa się wektoryzacją. NumPy bierze twoje polecenie i przekazuje właściwe obliczenia do prekompilowanego kodu w C. Kod w C przelatuje przez ten ciągły blok pamięci z prędkością sprzętową. Pomija sprawdzanie typów i szukanie wskaźników dla poszczególnych elementów, ponieważ pamięć jest idealnie jednolita. Ceną za ten ogromny wzrost prędkości jest strukturalna sztywność. Ponieważ pamięć to jeden ciągły blok, nie możesz tak po prostu użyć append, żeby dodać nową liczbę do ndarray, tak jak robisz to z listą w Pythonie. Jeśli potrzebujesz większej tablicy, NumPy zazwyczaj musi zaalokować zupełnie nowy blok pamięci i skopiować do niego stare dane. Budujesz kontener o dokładnie takim rozmiarze, jakiego potrzebujesz, a potem odpalasz swoje operacje na całym bloku naraz. Standardowa lista w Pythonie to zbiór wyizolowanych obiektów rozsianych po pamięci. Z kolei ndarray w NumPy to gęsty, jednolity blok surowych danych, zaprojektowany do błyskawicznego przetwarzania przez zoptymalizowany kod w C. Jeśli podobają Ci się te odcinki i chcesz wesprzeć program, znajdź DevStoriesEU na Patreonie. To wszystko na dzisiaj. Dzięki za wysłuchanie i buduj dalej!
2

Przywoływanie tablic: Tworzenie i kształt

4m 26s

Ten odcinek bada, jak prawidłowo tworzyć wielowymiarowe tablice za pomocą wbudowanych funkcji. Dowiesz się, jak używać narzędzi takich jak zeros, arange i linspace, aby błyskawicznie generować zbiory danych.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 2 z 7. W data science rzadko wpisujesz dane ręcznie. Zamiast tego musisz generować ogromne puste siatki i zakresy liczbowe za pomocą jednego polecenia. Porozmawiajmy o wbudowanych funkcjach, które pozwolą ci tworzyć arraye od podstaw i kontrolować ich strukturę. Na początek szybka poprawka dotycząca ręcznego tworzenia. Kiedy konwertujesz standardową listę w Pythonie na array za pomocą podstawowej funkcji array, częstym błędem jest przekazywanie wielu oddzielnych argumentów, żeby utworzyć wiele wymiarów. Funkcja oczekuje pojedynczej sekwencji. Żeby utworzyć dwuwymiarowy array, przekazujesz jedną listę, która zawiera inne listy, a nie dwie oddzielne listy. Każdy array, który tworzysz, niesie ze sobą strukturalne metadane. Dwie właściwości mają tu największe znaczenie. Pierwsza to ndim, która mówi ci o liczbie osi, czyli wymiarów, jakie ma array. Płaska sekwencja ma ndim równe jeden, podczas gdy płaska siatka ma ndim równe dwa. Drugą właściwością jest shape. Shape to tuple liczb całkowitych, określający dokładny rozmiar arraya w każdym wymiarze. Jeśli masz macierz z dwoma wierszami i trzema kolumnami, jej shape to dwa na trzy. Długość tuple'a shape zawsze będzie równa wartości ndim. Tworzenie arrayów z istniejących list jest okej do małych testów, ale prawdziwa praca wymaga generowania arrayów programowo. Jeśli potrzebujesz placeholdera do późniejszego wypełnienia danymi, używasz funkcji zeros lub ones. Po prostu przekazujesz tuple shape do tych funkcji, a one zwracają array o dokładnie takiej strukturze, wypełniony w całości zerami lub jedynkami. Domyślnie te funkcje tworzą floaty, ale możesz to nadpisać, określając inny typ danych. Kiedy potrzebujesz sekwencji liczb, NumPy udostępnia dwa główne narzędzia. Pierwsze to arange, które działa bardzo podobnie jak standardowy range w Pythonie. Podajesz mu wartość początkową, wartość końcową i rozmiar kroku. Generuje ono array liczb oddzielonych od siebie o ten krok. O ile arange świetnie sprawdza się do liczb całkowitych, używanie go z krokami typu float może dawać nieprzewidywalne rezultaty ze względu na to, jak komputery radzą sobie z precyzją dziesiętną. Liczba elementów, które otrzymasz z powrotem, może się nieznacznie różnić w zależności od mikroskopijnych błędów zaokrągleń. To prowadzi nas do linspace, który rozwiązuje problem precyzji floatów. Zamiast definiować rozmiar kroku, definiujesz dokładną liczbę elementów, które chcesz uzyskać. Podajesz linspace wartość początkową, wartość końcową i całkowitą liczbę punktów. NumPy oblicza dokładne odstępy za ciebie. Wyobraź sobie scenariusz, w którym obliczasz funkcję matematyczną w określonym przedziale. Chcesz obliczyć funkcję na gładkiej siatce współrzędnych między zerem a jedynką. Używając linspace, możesz wygenerować dokładnie sto równomiernie rozmieszczonych współrzędnych w tym przedziale. Otrzymujesz idealnie rozłożony jednowymiarowy array, który gwarantuje, że uwzględnione są zarówno granice początkowe, jak i końcowe. I tu robi się ciekawie. Różnica między tymi dwoma generatorami sekwencji dyktuje twój workflow. Używaj arange, kiedy dokładny rozmiar kroku ma znaczenie, na przykład przy liczeniu liczb całkowitych co dwa, ale zawsze używaj linspace przy pracy z floatami i przedziałami, żeby mieć pewność, ile dokładnie punktów danych otrzymasz i precyzyjnie trafić w swoje granice. Dzięki za wysłuchanie — do usłyszenia następnym razem.
3

Pod maską: Pamięć, strides i widoki

3m 47s

Ten odcinek zagłębia się w wewnętrzną architekturę NumPy, skupiając się na buforze danych i strides. Dowiesz się, dlaczego operacje takie jak slicing i transponowanie są niemal natychmiastowe, ponieważ zwracają widoki pamięci, a nie kopie.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 3 z 7. Masz macierz z miliardem pikseli i musisz odwrócić jej wiersze i kolumny. Jeśli zrobisz to w standardowym Pythonie, twój komputer po prostu stanie podczas kopiowania gigabajtów danych. W NumPy ta operacja wykonuje się natychmiast. Zmiana kolejności tych pikseli tak naprawdę nie przenosi ani jednego bajtu w pamięci. Dzieje się tak dzięki temu, jak NumPy pod spodem obsługuje pamięć, strides i views. Aby zrozumieć, dlaczego NumPy jest tak szybki, musisz spojrzeć na wewnętrzną strukturę arraya. Array w NumPy nie jest pojedynczym, monolitycznym obiektem. Jest ściśle podzielony na dwie części. Pierwsza część to data buffer. To ciągły, płaski blok surowej pamięci. To po prostu jednowymiarowa linia bajtów siedząca w RAM-ie. Taki surowy buffer nie wie absolutnie nic o wierszach, kolumnach czy wymiarach. Druga część to metadane. To mały nagłówek, zaimplementowany pod spodem jako struktura w C, który mówi NumPy, jak interpretować tę surową linię bajtów. Metadane przechowują pointer do początku data buffera, typ danych, shape arraya oraz strides. Strides to mechanizm, który przekształca płaską linię pamięci w wielowymiarową siatkę. Pojedynczy stride to po prostu liczba bajtów, o które komputer musi przesunąć się do przodu w pamięci, aby znaleźć następny element wzdłuż konkretnej osi. Załóżmy, że masz dwuwymiarowy array 64-bitowych intów. Każdy int zajmuje osiem bajtów. Aby przesunąć się o jedną kolumnę w prawo, stride może wynosić osiem bajtów. Ale żeby przeskoczyć w dół do następnego wiersza, stride może wynosić osiemdziesiąt bajtów, ponieważ musi pominąć cały wiersz danych w surowym bufferze, aby znaleźć początek następnego. Wielu developerów zakłada, że kiedy robisz slice arraya, system alokuje nową pamięć i kopiuje wybrane dane. To nieprawda. Kiedy żądasz slice'a, NumPy zostawia surowy data buffer całkowicie nietknięty. Zamiast tego tworzy nowy nagłówek metadanych. Ten nowy nagłówek wskazuje na dokładnie ten sam blok pamięci, ale zmienia początkowy pointer i modyfikuje strides, aby pominąć wykluczone przez ciebie elementy. To nazywa się view. I tu jest kluczowa sprawa. Ponieważ surowe dane i metadane są trzymane osobno, operacje zmieniające shape lub kolejność arraya są niemal całkowicie darmowe. Wróćmy do transponowania tej ogromnej macierzy z miliardem pikseli. NumPy nie bierze tych danych i nie przestawia ich fizycznie. Po prostu zamienia wartości strides w nowym nagłówku metadanych. Liczba bajtów, które wcześniej pomijałeś, żeby znaleźć następny wiersz, staje się liczbą, którą pomijasz, żeby znaleźć następną kolumnę. Do twojego kodu zwracana jest zupełnie inna struktura arraya, ale to po prostu view patrzący na dokładnie tę samą fizyczną pamięć. To rozdzielenie jest fundamentem wydajności pamięciowej NumPy. Możesz pociąć ogromny dataset na dziesiątki nakładających się na siebie slice'ów, przekazać je do różnych funkcji i nie zużyć na same dane absolutnie żadnej dodatkowej pamięci. Generujesz tylko malutkie nagłówki metadanych. Jednak to oznacza, że współdzielisz state. Zmodyfikowanie wartości w slice'u zmieni oryginalny array, ponieważ pod nimi wszystkimi kryje się tylko jeden prawdziwy data buffer. Oddzielenie surowych bajtów od reguł, które nimi rządzą, oznacza, że twoje najcięższe transformacje danych to często tylko lekkie podmiany metadanych. To wszystko w tym odcinku. Dzięki za wysłuchanie i koduj dalej!
4

Funkcje uniwersalne: Matematyka bez pętli

3m 29s

Ten odcinek omawia funkcje uniwersalne (ufuncs) i sposób, w jaki wektoryzują one operacje. Dowiesz się, jak całkowicie wyeliminować pętle for w języku Python, stosując matematykę element po elemencie oraz redukcje oparte na osiach.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 4 z 7. Jeśli kiedykolwiek zdarzy ci się pisać pętlę for, żeby pomnożyć liczby w arrayu, przestań. To oznacza, że płacisz za overhead interpretera Pythona przy każdym pojedynczym elemencie, a twój kod działa sto razy wolniej niż powinien. Rozwiązaniem jest użycie universal functions do wykonywania obliczeń matematycznych bez pętli. Częstym błędem jest próba przekazania całego arraya do funkcji ze standardowego modułu math w Pythonie. Jeśli przekażesz milion odczytów z czujników do math dot sine, Python wyrzuci błąd. Standardowy moduł math rozumie tylko pojedyncze wartości skalarne. Żeby przetworzyć array, normalnie musiałbyś napisać pętlę. Ale Python jest językiem dynamicznym. Wewnątrz pętli interpreter sprawdza typ danych przy każdej pojedynczej iteracji, zanim obliczy wynik. Kiedy przetwarzasz ogromne zbiory danych, te drobne pauzy na type-checking sumują się, tworząc poważny bottleneck wydajnościowy. Universal function, czyli ufunc, rozwiązuje ten problem, automatycznie operując na arrayach element po elemencie. Kiedy wywołujesz ufunc, NumPy spycha wykonanie pętli do skompilowanego kodu w C. Ponieważ arraye w NumPy mają jeden, jednolity typ danych, kod w C nie musi pauzować i sprawdzać typów. Iteruje po ciągłych blokach pamięci i oblicza wynik tak szybko, jak tylko pozwala na to twój procesor. Spójrzmy na konkretny scenariusz. Masz array zawierający tysiące odczytów z czujników środowiskowych i musisz zaaplikować transformację matematyczną do wszystkich naraz. Zamiast pisać pętlę, po prostu przekazujesz cały array do universal function, takiej jak numpy dot exp albo numpy dot sin. Ufunc bierze twój array wejściowy, odpala szybką pętlę na poziomie C dla każdego pojedynczego elementu i zwraca zupełnie nowy array wypełniony przetransformowanymi odczytami. I to jest ta najważniejsza część. Ufuncs robią więcej niż tylko transformacje element po elemencie. Zawierają wbudowane metody do zwijania danych, całkowicie omijając Pythona przy agregacjach. Najpopularniejsza z nich to metoda reduce. Załóżmy, że zaaplikowałeś swoją transformację matematyczną, a teraz potrzebujesz całkowitej sumy całego arraya. Wywołujesz metodę reduce bezpośrednio na ufunc dodawania. Piszesz numpy dot add dot reduce i przekazujesz jej swój array. Metoda reduce pod spodem aplikuje operację dodawania do pierwszych dwóch elementów. Bierze tę sumę, dodaje ją do trzeciego elementu i kontynuuje ten schemat, aż cały array zostanie zwinięty do pojedynczej wartości skalarnej. Jeśli twoje dane mają wiele wymiarów, możesz kontrolować, jak to zwijanie się odbywa. Jeśli twoje odczyty z czujników tworzą dwuwymiarowego grida, gdzie wiersze to różne czujniki, a kolumny to pojedyncze timestampy, zredukowanie całego grida do jednej liczby niszczy tę strukturę. Podając argument axis, kontrolujesz kierunek operacji. Jeśli każesz metodzie reduce działać wzdłuż axis zero, zwinie ona wiersze, zostawiając ci array zawierający sumę wszystkich czujników dla każdego pojedynczego timestampa. Za każdym razem, gdy pozwalasz universal function natywnie obsłużyć iterację, zamieniasz wolne pętle w Pythonie na zoptymalizowane sprzętowo wykonywanie kodu w C. Dzięki za odsłuch. Do usłyszenia następnym razem!
5

Broadcasting: Magia niedopasowanych kształtów

3m 48s

Ten odcinek wyjaśnia dokładne zasady mechanizmu Broadcasting. Dowiesz się, jak NumPy koncepcyjnie rozciąga tablice o niedopasowanych kształtach, aby mogły być przetwarzane razem bez marnowania pamięci.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 5 z 7. Co się dzieje, gdy próbujesz pomnożyć trójwymiarową macierz miliona pikseli przez pojedynczą małą tablicę trzech liczb? W wielu ściśle typowanych językach dostajesz błąd shape mismatch. W NumPy to po prostu działa. To zachowanie nazywa się broadcastingiem. Broadcasting opisuje sposób, w jaki NumPy traktuje tablice o różnych shape'ach podczas operacji arytmetycznych. Bierze mniejszą tablicę i koncepcyjnie rozciąga ją na większą, tak aby ich shape'y idealnie się pokrywały. Słuchacze często błędnie myślą, że to rozciąganie fizycznie kopiuje dane, aby zbudować nową, pasującą tablicę w pamięci. Tak nie jest. NumPy obsługuje to wyrównanie niejawnie na poziomie C. Iteruje po tych samych elementach wielokrotnie z zerowym memory overhead, co sprawia, że broadcasting jest niezwykle szybki i wysoce wydajny. Aby z niego korzystać, musisz zrozumieć, jak NumPy decyduje, czy dwie tablice są kompatybilne. Nie patrzy na całkowitą liczbę elementów. Patrzy na tuple shape'ów. NumPy ustawia w jednej linii shape'y obu tablic i porównuje je, zaczynając od trailing dimensions – czyli tych po prawej stronie – i przesuwa się w lewo. Dwa wymiary są kompatybilne, jeśli spełniają jeden z dwóch ścisłych warunków. Muszą być dokładnie równe, albo jeden z nich musi wynosić jeden. Jeśli w którymkolwiek momencie porównania żaden z tych warunków nie zostanie spełniony, NumPy rzuca ValueError i operacja kończy się błędem. Zastosujmy to do konkretnego scenariusza. Pracujesz z obrazem RGB. Ładujesz go do tablicy NumPy o shape'ie 256 na 256 na 3. Trójka na końcu shape'a reprezentuje kanały kolorów: czerwony, zielony i niebieski. Teraz musisz zrobić korekcję kolorów tego obrazu, skalując każdy kanał inaczej. Definiujesz jednowymiarową tablicę zawierającą trzy wagi korekcji kolorów. Shape tej tablicy to po prostu 3. Kiedy mnożysz ogromną tablicę obrazu przez małą tablicę wag, NumPy stosuje regułę od prawej do lewej. Umieszcza shape obrazu, 256 na 256 na 3, nad shape'em wag, który wynosi tylko 3. Zaczynając od skrajnej prawej strony, porównywane są trailing dimensions. Oba wynoszą 3. Ponieważ są równe, są kompatybilne. Następnie NumPy przesuwa się w lewo. Tablica obrazu ma wymiar 256, ale tablicy wag całkowicie skończyły się wymiary. W tym momencie wkracza druga część reguły. Gdy jedna tablica ma mniej wymiarów niż druga, NumPy niejawnie dodaje jedynki na początku jej shape'a, aż zrównają się długością. Shape tablicy wag jest traktowany jako 1 na 1 na 3. Teraz porównanie trwa dalej. Obraz ma wymiar 256, a tablica wag ma teraz wymiar 1. Ponieważ jeden z nich to 1, są kompatybilne. Tablica wag jest koncepcyjnie rozciągana w dół na 256 wierszy. To samo dzieje się dla kolejnego wymiaru. Shape'y się zgadzają, a NumPy płynnie aplikuje twoje trzy wagi kolorów do wszystkich sześćdziesięciu pięciu tysięcy pikseli. I to jest najważniejsza część. Reguła działa tylko od prawej do lewej. Jeśli masz dwuwymiarową tablicę o shape'ie 5 na 4 i próbujesz dodać tablicę o shape'ie 5, możesz pomyśleć, że rozciągnie się ona na kolumny. Tak się nie stanie. Zaczynając od prawej, NumPy porównuje 4 i 5. Nie są równe, a żadna z nich nie jest jedynką. Operacja natychmiast kończy się błędem. Aby to zadziałało, musiałbyś najpierw zrobić reshape drugiej tablicy do 5 na 1. Broadcasting pozwala ci pisać czysty kod bez pętli, który wykonuje się z prędkością skompilowanego kodu. Złota zasada to zawsze trailing dimensions na pierwszym miejscu: muszą się dokładnie zgadzać, albo jeden z nich musi być jedynką. Dzięki za wysłuchanie. Do usłyszenia następnym razem!
6

Precyzyjne filtrowanie: Maskowanie logiczne

3m 41s

Ten odcinek skupia się na zaawansowanym maskowaniu logicznym do filtrowania złożonych zbiorów danych. Dowiesz się, jak wyodrębniać bardzo specyficzne punkty danych z ogromnych tablic za pomocą prostej logiki warunkowej.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 6 z 7. Wyciągnięcie każdej liczby ujemnej z arraya o miliardzie elementów nie jest problemem wyszukiwania. Nie powinno to wymagać użycia pętli i na pewno nie powinno spowalniać twojej aplikacji. Tutaj z pomocą przychodzi precyzyjne filtrowanie: boolean masking. Zwykły slicing wyciąga równe, przewidywalne kawałki danych. Ale dane w prawdziwym świecie są chaotyczne. Rzadko potrzebujesz po prostu pierwszych dziesięciu elementów. Szukasz konkretnych elementów na podstawie warunku logicznego, a te mogą być losowo rozsiane po całym twoim datasecie. W NumPy wyciąganie danych w ten sposób to tak zwane advanced indexing, a boolean masking to jedna z jego najpotężniejszych form. Weźmy ogromny array z odczytami temperatury. Musisz wyizolować tylko te wartości, które spadają poniżej zera. Zamiast pisać pętlę, żeby sprawdzać każdy pojedynczy odczyt, używasz maski. Maska to array wartości typu boolean, czyli True albo False. Co kluczowe, ten boolean array ma dokładnie taki sam shape jak twój oryginalny array z danymi. Tworzysz ją, nakładając warunek mniejsze niż zero bezpośrednio na array. NumPy błyskawicznie ewaluuje każdy pojedynczy element. Jeśli temperatura jest poniżej zera, maska zapisuje True dokładnie na tej pozycji. Jeśli temperatura wynosi zero lub więcej, zapisuje False. Kiedy masz już tę maskę, aplikujesz ją z powrotem na oryginalny array, przekazując ją dokładnie tam, gdzie normalnie wstawiasz index. NumPy czyta maskę, wybiera każdy element, dla którego maska to True, i odrzuca resztę. Ta operacja zwraca zupełnie nowy, jednowymiarowy array, zawierający wyłącznie twoje temperatury poniżej zera. Dzieje się to w jednym kroku, wykonywanym pod spodem przez wysoce zoptymalizowany kod w C. Zwróć uwagę na tę różnicę, bo wielu developerów się na tym łapie. Musisz dokładnie wiedzieć, co NumPy ci zwraca, kiedy filtrujesz dane. Zwykły slicing zwraca view. Jeśli zrobisz slice pierwszych dziesięciu elementów arraya i je zmodyfikujesz, oryginalny array też się zmieni. Boolean masking zachowuje się zupełnie inaczej. Ponieważ boolean masking to forma advanced indexing, zawsze zwraca kopię danych, a nigdy view. Powodem jest architektura pamięci. Kiedy robisz slicing, NumPy po prostu zmienia pointery na istniejący, ciągły blok pamięci. Ale kiedy nakładasz boolean mask, wybrane przez ciebie elementy są całkowicie arbitralne. Nie leżą już równiutko obok siebie w pamięci. NumPy musi je zebrać i zaalokować nową przestrzeń, żeby zapisać wynik. To oznacza, że jeśli zmodyfikujesz swój nowo przefiltrowany array z temperaturami poniżej zera, twój oryginalny dataset pozostanie całkowicie nietknięty. Możesz też chainować te warunki. Jeśli potrzebujesz temperatur poniżej zera, ale ściśle powyżej minus dziesięciu stopni, łączysz te dwa warunki logiczne. NumPy ewaluuje połączoną logikę element po elemencie i buduje jedną, precyzyjną maskę. Kiedy aplikujesz boolean mask, wymieniasz wydajność pamięciową view na absolutną precyzję filtrowania, co daje ci czystą, niezależną kopię dokładnie tych danych, o które prosiłeś. To wszystko w tym odcinku. Dzięki za słuchanie i twórz dalej!
7

Uniwersalny tłumacz: Interoperacyjność

4m 11s

Ten odcinek ujawnia, dlaczego NumPy pozostaje kręgosłupem analizy danych w języku Python. Dowiesz się, jak DLPack i array interface pozwalają na współdzielenie pamięci zero-copy między narzędziami takimi jak Pandas i PyTorch.

Pobierz
Cześć, tu Alex z DEV STORIES DOT EU. NumPy, odcinek 7 z 7. Skoro nowoczesne frameworki GPU odwalają czarną robotę w machine learningu, mógłbyś pomyśleć, że NumPy staje się przestarzały. Możesz nawet zakładać, że konwersja CPU tensora z PyTorch na array w NumPy wymaga powolnego kopiowania pamięci, co spowalnia twój pipeline. Nic bardziej mylnego. W rzeczywistości NumPy działa jak uniwersalny tłumacz: interoperacyjność, która spaja cały ekosystem danych w Pythonie. Pomyśl o standardowym pipeline w machine learningu. Używasz Pandas do załadowania i wyczyszczenia ogromnego tabelarycznego datasetu. Wyciągasz te wartości do NumPy, żeby nałożyć specjalistyczny filtr matematyczny. Na koniec przekazujesz te przefiltrowane dane do PyTorch, żeby wytrenować sieć neuronową. Gdyby każda z tych bibliotek izolowała swoje dane, przechodzenie od kroku do kroku oznaczałoby ciągłe duplikowanie całego datasetu w pamięci. Wyczerpałbyś swój RAM i zmarnował czas procesora na samo przerzucanie bajtów. Zamiast tego, dzięki magii interoperacyjności, dane tak naprawdę wcale się nie ruszają. Pandas, NumPy i PyTorch po prostu współdzielą dokładnie ten sam pointer w pamięci. Kiedy PyTorch czyta dane, patrzy na dokładnie te same fizyczne adresy w pamięci, które pierwotnie zaalokował Pandas. To współdzielenie typu zero copy jest możliwe dzięki standardowym protokołom pamięci. Podstawowym z nich jest array interface. Jeśli obiekt w Pythonie wystawia ten interfejs, w zasadzie przekazuje mały słownik z metadanymi. Te metadane mówią NumPy, gdzie dokładnie w pamięci zaczynają się surowe dane, jaki mają shape i jaki data type przechowują. Kiedy wywołujesz funkcję NumPy na kompatybilnym obiekcie, NumPy czyta te instrukcje i obudowuje istniejący blok pamięci własną strukturą array. Nie tworzy nowego array; po prostu tworzy nowy view na stare dane. I tu jest kluczowa sprawa. Oryginalny array interface został zaprojektowany głównie dla standardowej pamięci systemowej. Kiedy data science przeniosło się na akceleratory sprzętowe, ekosystem potrzebował sposobu na współdzielenie danych żyjących na GPU lub dedykowanych chipach, bez konieczności przepuszczania ich z powrotem przez CPU. To doprowadziło do adopcji DLPack. DLPack to nowoczesny, otwarty standard do współdzielenia wielowymiarowych array pomiędzy różnymi frameworkami. Definiuje stabilną strukturę, którą każda biblioteka może wyprodukować i skonsumować. Jeśli masz tensor we frameworku takim jak PyTorch czy JAX, możesz go wyeksportować używając protokołu DLPack. NumPy może go wtedy płynnie wchłonąć za pomocą swojej dedykowanej funkcji from dlpack. Chociaż sam NumPy operuje głównie na CPU, jego wsparcie dla DLPack oznacza, że może działać jako centralny hub do routingu. Możesz przekazać obiekt DLPack z frameworka do deep learningu do NumPy, albo z NumPy z powrotem do frameworka, a to wszystko bez kosztownej duplikacji danych. NumPy już dawno przestał być tylko biblioteką matematyczną. To niewidzialny standard pamięci, który zapobiega fragmentacji ekosystemu danych w Pythonie na odizolowane, niekompatybilne wyspy. Zachęcam cię do przejrzenia oficjalnej dokumentacji, wypróbowania tych konwersji zero copy w praktyce we własnym terminalu, albo odwiedzenia devstories dot eu, żeby zasugerować tematy do naszych przyszłych serii. To wszystko w tym odcinku. Dzięki za wysłuchanie i koduj dalej!