Python: Numpy: Broadcasting na przykładach

15-sty-2020

W uczeniu maszynowym często wykonujemy operacje na macierzach (wektor to też przykład macierzy, tylko że jeden z wymiarów wynosi 1). Cchcemy takie operacje wykonywać, gdzie tylko się da przy pomocy funkcji z modułu numpy, bo tak jest efektywniej!

Matematyka jest bezlitosna. Istnieją pewne określone reguły, określające warunki kiedy na macierzach można wykonywać operacje:

  • aby dodać do siebie macierze/wektory, muszą one mieć takie same wymiary
  • aby pomnożyć przez siebie dwie macierze, pierwsza z nich musi  mieć tyle kolumn, co druga wierszy

Tymczasem u nas te warunki nie zawsze będą spełnione. Można się jednak „umówić”, co do tego jak wykonywać operacje, których, matematycznie rzecz ujmując, nie można wykonywać. Taki mechanizm w numpy nosi nazwę broadcasting, bo określa on, jak należy powiększyć mniejszą macierz do rozmiarów pasujących do większej macierzy, aby w intuicyjny sposób wykonać operację na macierzach.

Żeby uniknąć matematycznego hejtu, na usprawiedliwienie należy dodać, że broadcasting-u można by uniknąć. W takim przypadku programista musiałby po prostu samodzielnie dbać o to, żeby macierze miały właściwe rozmiary. Taka praca, nie dość że nudna, to jeszcze spowoduje znaczne użycie pamięci. Dlatego tak się palimy do zautomatyzowanego i zoptymalizowanego procesu broadcastu.

Zadanie:

Zacznijmy od czegoś prostego. Oto dwa wektory:

import numpy as np

a = np.array([1, 2, 3])
print(a)
b = np.array([10,20,30])
print(b)

Dodaj do siebie te dwa wektory. Nim to zrobisz, zastanów się:

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

To jest poprawne działanie matematyczne.

Operacja dodawania może wyglądać tak:

c = a + b
print(c)

Zadanie:

A co wydarzyłoby się, gdyby b był po prostu liczbą? Jak dodać do siebie wektor a i liczbę b?

a = np.array([1, 2, 3])
print(a)
b = 100
print(b)

Zastanów się

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

To nie jest poprawne matematycznie… do wektora nie można dodawać liczby… ale na intuicję by się dało! Wystarczyło by powtórzyć wartość 100 trzy razy i zrobić z b wektor [100, 100, 100]

Operacja dodawania może wyglądać tak:

c = a + b
print(c)

Tu właśnie po raz pierwszy skorzystaliśmy z broadcastingu. Jeśli masz do wykonania operację na dwóch tablicach (wektorach) i jeden z nich jest dłuższy od drugiego, to ten drugi „poszerzamy” powtarzając jego wartość.

Zadanie:

A co wydarzyłoby się, gdyby b był po prostu krótszym wektorem, ale nie liczbą?  Jak dodać do siebie dwa wektory o różnej długości?

a = np.array([1, 2, 3])
print(a)
b = [100, 200]
print(b)

Zastanów się

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

To nie jest poprawne matematycznie… aby do wektora dodać wektor, oba wektory musza mieć ten sam wymiar. Chwyt z powieleniem jednej liczby tu się nie uda, bo wektor b składa się z 2 wartości…

Operacja dodawania w poniższej postaci skończy się błędem:

c = a + b
# ValueError: operands could not be broadcast together with shapes (3,) (2,)

Broadcasting działa tylko w przypadku gdy podczas powiększania mniejszego wektora miał on wymiar 1 – czyli tu musiał być liczbą.

Zadanie:

No to przejdźmy to macierzy 2-wymiarowych. Poniżej definiujemy dwie macierze o takich samych wymiarach. Jak je do siebie dodać?

a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
b = np.array([[101, 102, 103], [104, 105, 106]])
print(b)

Zastanów się

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

Ponieważ wymiary macierzy są zgodne, to takie dodawanie jest poprawne matematycznie i najłatwiej zapisać je w postaci:

c = a + b

Zadanie:

A co wydarzyłoby się, gdyby b był po prostu liczbą? Jak dodać do siebie dwuwymiarową macierz a i liczbę b?

a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
b = 100
print(b)

Zastanów się

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

To nie jest poprawne matematycznie… do macierzy nie można dodawać liczby… ale na intuicję by się dało! Wystarczyło by powtórzyć wartość 100 w pomocniczej macierzy, o takich samych wymiarach jak a

Operacja dodawania może wyglądać tak:

c = a + b
print(c)

Tu znowu skorzystaliśmy z broadcastingu. Jeśli masz do wykonania operację na macierzy i liczbie to liczba została powielona tyle razy, aby utworzyć macierz pasującą wymiarami do oryginalnej macierzy, a następnie zostało wykonane dodawanie

Zadanie:

A co wydarzyłoby się, gdyby b był wektorem? Jak dodać do siebie dwuwymiarową macierz a i wektor b? Można tu rozważyć, co najmniej dwa wypadki. Raz b będzie „poziomym wektorem” o długości 3, co pasuje do 3 kolumn macierzy a, a raz b będzie „pionowym wektorem” o długości 2, co pasuje do liczby wierszy w macierzy a

# case 1
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
b = np.array([100, 200, 300])
print(b)

# case 2
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
b = np.array([[100], [200]])
print(b)

Zastanów się

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

To nie jest poprawne matematycznie… można do siebie dodawać tylko macierze o takich samych wymiarach, … ale na intuicję by się dało! Wystarczyło by powtórzyć

  • w pierwszym przypadku kilka razy wiersz b
  • a w drugim przypadku kilka razy kolumnę b

i wykonać dodawanie takich poszerzonych macierzy!

Operacja dodawania może wyglądać tak:

c = a + b
print(c)

Tu znowu skorzystaliśmy z broadcastingu. Jeśli masz do wykonania operację na macierzy i wektorze to ten wektor zostanie powielony tyle razy ile trzeba, aby wykonać obliczenia.

Zadanie:

A co wydarzyłoby się, gdyby b był macierzą 2×2? Jak dodać do siebie dwuwymiarową macierz a i wektor b o różnych wymiarach?

a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
b = np.array([[100, 200],[300,400]])
print(b)

Zastanów się

  • czy takie działanie jest poprawne matematycznie?
  • jak najwygodniej zapisać dodawanie takich dwóch wektorów?

Rozwiązanie:

To nie jest poprawne matematycznie… można do siebie dodawać tylko macierze o takich samych wymiarach, … na dodatek, tutaj broadcasting nie zadziała, bo broadcasting może powielać tylko wektory jednowymiarowe

Poniższe polecenie zakończy się błędem

c = a + b
print(c)
# ValueError: operands could not be broadcast together with shapes (2,3) (2,2)

Podsumowanie:

W tym LAB poznaliśmy zasadę broadcastingu. Pozwala ona w wygodny sposób wykonywać obliczenia na macierzach i wektorach.

Więcej o broadcastingu można przeczytać w artykule dokumentacji modułu numpy:

https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html

Komentarze:

  1. MH napisał,

    Hej Rafał,

    Bardzo fajny artykuł. Teraz uczę się trochę o macierzach z kursów eTrapez.pl i ten artykuł trochę mi pomógł w zrozumieniu zagadnienia.

    Na razie jestem początkujący w programowaniu w Python i materiał tego artykułu wydaje mi się trochę za bardzo zaawansowany dla mnie, jednak i tak warto było przeczytać.

    Dziękuję za dzielenie się swoją wiedzą i pozdrawiam :-).

Autor: Rafał Kraik