NUMPY

Array initialization

In [365]:
import numpy as np

# Uwaga: b jest tablicą liczb zmiennoprzecinkowych, pomimo, że podaliśmy liczby całkowite 
a = np.array([1,2,3])
print(a)
b = np.array([1,2,3],dtype=np.float32)
print(b)
[1 2 3]
[1. 2. 3.]

Także możemy przekształcać list z python na tablicę numpy:

In [366]:
l = [1,2,3] # także można użyć tuple 
print(type(l))
a = np.array(l)
print(a)
b = np.array(l, dtype=np.float32)
print(b)
<class 'list'>
[1 2 3]
[1. 2. 3.]

Możemy też tworzyć tablice wielowymiarowe:

In [367]:
a = np.array([[1,2,3], [4,5,6]])
print("tablica a: ")
print(a)
print(type(a[0]))

#Uwaga: wymiary muszą się zgadzać, inaczej numpy potraktuje to jak tablicę python list:
b = np.array([[1,2,3], [4,5]])
print("tablica b: ")
print(b)
print(type(b[0]))
tablica a: 
[[1 2 3]
 [4 5 6]]
<class 'numpy.ndarray'>
tablica b: 
[list([1, 2, 3]) list([4, 5])]
<class 'list'>

Są też funkcje: np.ones, np.zeros, np.eye i np.arange, których używamy do tworzenia tablic wypełnionych jedynkami, zerami, macierz identyczności i liczb z przedzału z odpowiednim krokiem odpowiednio:

In [368]:
a = np.zeros((4,5))
print(a, '\n')

b = np.eye(4)
print(b, '\n')

r = np.arange(2, 3, 0.2)

print(r, '\n')

c = np.ones((4,5,3)) # możemy tworzyć macierze o dowolnych wymiarach
print(c) 
[[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]] 

[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]] 

[2.  2.2 2.4 2.6 2.8] 

[[[1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]]

 [[1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]]

 [[1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]]

 [[1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]
  [1. 1. 1.]]]

Operacje na tablicach Numpy

W numpy możemy wybierać elementy tablicy jak w zwykłej list z python:

In [369]:
a = np.array([[1,2,3], [4,5,6]])
print(a[0]) # zwraca [1 2 3]

print(a[1, 1]) # zwraca 5
#równoważny zapis 
# print(a[1][1])
[1 2 3]
5

Także możemy zrobić array slicing, która ogólnie wygląda tak a[l, r] i zwraca elementy z zakresu [l, r) :

In [370]:
a = np.array([[ 0,  1,  2,  3], # 0
              [ 4,  5,  6,  7], # 1
              [ 8,  9, 10, 11], # 2
              [12, 13, 14, 15], # 3
              [16, 17, 18, 19]]) # 4
               #0  #1  #2  #3
    
print(a[2:]) #zwraca wiersze od indeksu 2 do końca
[[ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
In [371]:
print(a[:2]) #zwraca wiersze od indeksu 0 do 2 wyłącznie
[[0 1 2 3]
 [4 5 6 7]]
In [372]:
print(a[2:4]) #zauważmy że żeby dostać jeszcze ostatni wiersz musimy podać a[2:5]
[[ 8  9 10 11]
 [12 13 14 15]]
In [373]:
print(a[2:4, 1:3])
#równoważne z
# b = a[2:4]
# print(b[:, 1:3]) # b[:] zwraca wszystkie wiersze
[[ 9 10]
 [13 14]]
In [374]:
print(a[:, 2]) #z każdego wiersza wybiera element o indeksie 2 
[ 2  6 10 14 18]

Można także używać booli jako maskę dla wybierania elementów

In [375]:
mask = [True, True, True, False]
print(a[:, mask])
[[ 0  1  2]
 [ 4  5  6]
 [ 8  9 10]
 [12 13 14]
 [16 17 18]]

Żeby zobaczyć jakiego rozmiaru jest tablica używamy zmiennej shape, która zwraca tuple z wymiarami:

In [376]:
print(a.shape)
print(a.shape[1]) #długość wiersza 
(5, 4)
4
In [377]:
# macierz a jest z poprzedniago przykładu
print(a.reshape(-1), '\n') # a.flatten()
print(a.reshape(4,5), '\n')
print(a.reshape(2,10), '\n')
# print(a.reshape(2,8)) #ValueError: cannot reshape array of size 20 into shape (2,8)
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19] 

[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]] 

[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]] 

In [378]:
a = np.ones((300, 32, 32, 3))
print(a.shape,'\n')
print(a.reshape(300, -1).shape) # 32*32*3 = 3072
print(a.reshape(300, 32, -1, 3).shape)
(300, 32, 32, 3) 

(300, 3072)
(300, 32, 32, 3)

To jest użyteczne do podobnych operacij (broadcasting, numpy functions):

In [379]:
a = np.random.randn(2,3,4) # numpy functions
print(a,'\n')
print(a<0,'\n') # broadcasting
a[a<0] = 0
print(a)
[[[-0.6448891   0.95988904 -0.79272592 -0.06970128]
  [-0.97003444  0.92054643 -0.65928497 -0.47546803]
  [-0.15718798  1.3399144  -0.71455218  0.01069881]]

 [[-1.08111028  0.73841436  0.28729008 -0.08504842]
  [-0.56170564  1.31879558 -0.54805955 -0.55464583]
  [ 0.59014749 -2.37489079  0.40210844 -0.41835986]]] 

[[[ True False  True  True]
  [ True False  True  True]
  [ True False  True False]]

 [[ True False False  True]
  [ True False  True  True]
  [False  True False  True]]] 

[[[0.         0.95988904 0.         0.        ]
  [0.         0.92054643 0.         0.        ]
  [0.         1.3399144  0.         0.01069881]]

 [[0.         0.73841436 0.28729008 0.        ]
  [0.         1.31879558 0.         0.        ]
  [0.59014749 0.         0.40210844 0.        ]]]

Możemy wykorzystywać list albo np.array jak listę elementów do pobrania:

In [380]:
a = np.array([[ 0,  1,  2,  3],
              [ 4,  5,  6,  7],
              [ 8,  9, 10, 11],
              [12, 13, 14, 15],
              [16, 17, 18, 19]])
b = np.array([0,2,3])
print(a[:, [0,1,3]],'\n') #pobiera z każdego wiersza elementy o indeksach 0,2,3
print(a[b, :]) # bierze wszystkie elementy z wierszów o indeksach 0,2,3
[[ 0  1  3]
 [ 4  5  7]
 [ 8  9 11]
 [12 13 15]
 [16 17 19]] 

[[ 0  1  2  3]
 [ 8  9 10 11]
 [12 13 14 15]]
In [381]:
a = np.array([[ 0,  1,  2,  3],
              [ 4,  5,  6,  7],
              [ 8,  9, 10, 11],
              [12, 13, 14, 15],
              [16, 17, 18, 19]])

# elementy nie muszą być unikalne jak w przykładzie
b = a[:, [2,2,2]]
print(b, '\n')

# b jest nową tablicą więc wszystkie zmiany b nie wpłyną na a
b[0,0] = 55

print(b,'\n')
print(a)
[[ 2  2  2]
 [ 6  6  6]
 [10 10 10]
 [14 14 14]
 [18 18 18]] 

[[55  2  2]
 [ 6  6  6]
 [10 10 10]
 [14 14 14]
 [18 18 18]] 

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]

Uwaga operacja b = a nie będzie operacją kopiowania, i b zarowno jak i a będą wskazywać na tą samą macierz

In [382]:
a = np.array(range(5)) # [0 1 2 3 4]
b = a
b[1] = 23
print("macierz b",'\n', b, '\n')
print("macierz a",'\n', a, '\n')
macierz b 
 [ 0 23  2  3  4] 

macierz a 
 [ 0 23  2  3  4] 

Żeby tego uniknąć można użyć funkcji np.copy()

In [383]:
a = np.array(range(5)) # [0 1 2 3 4]
b = a.copy() # lub np.copy(a)
b[0]=10 # b = [10 1 2 3 4]
print(a)
[0 1 2 3 4]

Numpy broadcsting

Broadcasting jest potężnym mechanizmem w Numpy który pozwala wykonywać operacje na macierzach w szybki i zręczny sposób:

In [384]:
a = np.arange(20).reshape(5, 4) #[[ 0  1  2  3]
                                   #[ 4  5  6  7]
                                   #[ 8  9 10 11]
                                   #[12 13 14 15]
                                   #[16 17 18 19]]

b = np.array([1, 2, 3, 4])

a += b # a = a + b
print(a, '\n')
a -= b
print(a,'\n')
[[ 1  3  5  7]
 [ 5  7  9 11]
 [ 9 11 13 15]
 [13 15 17 19]
 [17 19 21 23]] 

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]] 

operacja a += b może być jawnie zapisana w ten sposób:

In [385]:
for i in range(a.shape[0]):
    for j in range(a.shape[1]):
        a[i, j] += b[j]
print(a, '\n')

a -= b
[[ 1  3  5  7]
 [ 5  7  9 11]
 [ 9 11 13 15]
 [13 15 17 19]
 [17 19 21 23]] 

In [386]:
a *= b
print(a, '\n')
a //= b 
print(a, '\n')

# a /= b # Uwaga w numpy używanie /= dla np.int64(takiego typu są liczby całkowite defaultowo) spowoduje błąd 

# funkcja np.astype przyjmuje jako argument typ i rzutuje(konwertuje) elementy tablicy na ten typ
a = np.arange(20).astype(np.float64).reshape(5, 4) #[[ 0  1  2  3]
                                                   #[ 4  5  6  7]
                                                   #[ 8  9 10 11]
                                                   #[12 13 14 15]
                                                   #[16 17 18 19]]

a /= b
print(a)
[[ 0  2  6 12]
 [ 4 10 18 28]
 [ 8 18 30 44]
 [12 26 42 60]
 [16 34 54 76]] 

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]] 

[[ 0.          0.5         0.66666667  0.75      ]
 [ 4.          2.5         2.          1.75      ]
 [ 8.          4.5         3.33333333  2.75      ]
 [12.          6.5         4.66666667  3.75      ]
 [16.          8.5         6.          4.75      ]]

Można używać broadcast nie tylko dlo wierszy:

In [387]:
a = np.arange(20).reshape(5, 4) #[[ 0  1  2  3]
                                #[ 4  5  6  7]
                                #[ 8  9 10 11]
                                #[12 13 14 15]
                                #[16 17 18 19]]
                
b = np.arange(5).reshape(5,1) #[[0],
                              # [1],
                              # [2],
                              # [3],
                              # [4]]

a = a + b # do wszystkich elementów pirwszego wiersza(o indeksie 0) będzie dodane 0, do drugiego wiersza 1, ...

print(a)
[[ 0  1  2  3]
 [ 5  6  7  8]
 [10 11 12 13]
 [15 16 17 18]
 [20 21 22 23]]

Uwaga dla broadcast'u wymiary pierwszej i drugiej macierzy muszą się zgadzać dla osi według ktorej operacja będzie wykonana, np.:

In [388]:
a = np.arange(20).reshape(5, 4)
b = np.arange(5) # [0 1 2 3 4] 
print(a.shape, '\n', b.shape)

a = a + b
(5, 4) 
 (5,)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-388-cc14a7d6b83b> in <module>
      3 print(a.shape, '\n', b.shape)
      4 
----> 5 a = a + b

ValueError: operands could not be broadcast together with shapes (5,4) (5,) 

Często potrzebujemy dodać/pomnożyć/.. każdy element przez skalar, w numpy nie potrzebujemy dla tego żadnej pętli, również możemy użyć operatorów porównywania, jak w jednym z poprzednich przykładów (wróć do przykładu):

In [389]:
a = np.zeros((3,2)) #[[0. 0.]
                    #[0. 0.]
                    #[0. 0.]]
a += 2
print(a,'\n')
a /= 6
print(a,'\n')

a += [0,1] #także broadcasting działa na list'ach, ale polecam używać np.array, żeby nie dostać problemów z kastowaniem typów 
print(a, '\n')

b = a > 1
print(b)
[[2. 2.]
 [2. 2.]
 [2. 2.]] 

[[0.33333333 0.33333333]
 [0.33333333 0.33333333]
 [0.33333333 0.33333333]] 

[[0.33333333 1.33333333]
 [0.33333333 1.33333333]
 [0.33333333 1.33333333]] 

[[False  True]
 [False  True]
 [False  True]]

Numpy functions

In [390]:
print(np.random.rand(3,4,2)) # randomowe liczby z zakresu 0..1
[[[0.95654898 0.51772985]
  [0.30443877 0.66300355]
  [0.5346792  0.42737866]
  [0.01869313 0.71600861]]

 [[0.58592717 0.25488915]
  [0.18962555 0.69029206]
  [0.65188959 0.87090875]
  [0.92873514 0.33361594]]

 [[0.8189228  0.62384474]
  [0.94660751 0.16215483]
  [0.92421993 0.29920311]
  [0.39280279 0.40642805]]]
In [391]:
print(np.random.randn(3, 4, 2)) # liczby z "standard normal distribution"
[[[ 0.12585249 -0.55134048]
  [ 0.25200496 -1.50630257]
  [-0.01470187 -0.05461041]
  [ 0.74866057 -0.83361785]]

 [[-0.27943147  0.0164368 ]
  [ 0.63081656  0.17151957]
  [-0.61570685 -0.15984513]
  [ 1.76090931 -0.36132852]]

 [[ 1.15908714 -0.60243523]
  [ 0.00741905 -0.80679109]
  [ 0.10368366 -0.41862015]
  [-0.35824732  0.10183025]]]
In [4]:
low, high = (-6, 1)
shape = (3,4,2)
print(np.random.uniform(low, high, shape)) # "uniform distribution" z zakresu od low do high, z podanym shape typu tuple
[[[-1.16558105 -3.53384009]
  [-5.48569627 -4.24534522]
  [-0.27918469 -0.25560944]
  [-5.48176218  0.70471729]]

 [[-3.39664205 -4.94766537]
  [-5.26482679 -2.45064224]
  [-3.69986093 -0.30885886]
  [ 0.53846226 -0.21632664]]

 [[-5.74475269  0.42878536]
  [-0.61880348 -0.66429102]
  [-5.15524829 -1.4232551 ]
  [-2.83124314 -4.03850157]]]
In [393]:
a = np.random.uniform(-1, 1, (3,2))
print(a,'\n')

print(np.mean(a),'\n') # a.mean()
print(np.mean(a, axis=0), '\n') # liczy śriednią dla każdej kolumny 
print(np.mean(a, axis=1), '\n') # liczy śriednią dla każdego wiersza 

# podobnie działa std
print(np.std(a), '\n')
print(np.std(a, axis=0), '\n')
print(np.std(a, axis=1),'\n')
[[-0.04117204 -0.99369902]
 [ 0.80944333 -0.80993573]
 [-0.64197097  0.47084097]] 

-0.2010822418751138 

[ 0.04210011 -0.44426459] 

[-5.17435530e-01 -2.46196812e-04 -8.55649991e-02] 

0.6697687179800678 

[0.59545588 0.65141174] 

[0.47626349 0.80968953 0.55640597] 

Numpy linear algebra numpy.linalg

In [5]:
import numpy.linalg as linalg

a = np.array([[2,3],
              [2,2]])

print(linalg.inv(a), '\n') # macierz odwrotna Uwaga: wyrzuca błąd kiedy det == 0
print(linalg.det(a), '\n') # wyznacznik
print(linalg.norm(a), '\n') # norma
print(linalg.norm(a, axis=0), '\n') # norma z kolumn jako wektorów 
print(linalg.norm(a, axis=1), '\n') # norma z wierszy jako wektorów
[[-1.   1.5]
 [ 1.  -1. ]] 

-2.0 

4.58257569495584 

[2.82842712 3.60555128] 

[3.60555128 2.82842712] 

Operacje na macierzach

In [395]:
a = np.arange(6).reshape(2,3)
b = np.arange(6).reshape(3,2)
print(a,'\n\n', b, '\n')

print("------------------------")
print(np.dot(a,b), '\n') # mnożenie macierzowe
print(a.dot(b), '\n') # inna forma
print(a @ b, '\n') #inna forma
print("------------------------")

#Uwaga 
print(a*a, '\n') # element-wise multiplication czyli 
                 # result[i,j] = first_matrix[i,j] * second_matrix[i,j] (broadcasting mnożenia)
#-------------------------------

print(a.T, '\n') # transponowanie macierzy 
[[0 1 2]
 [3 4 5]] 

 [[0 1]
 [2 3]
 [4 5]] 

------------------------
[[10 13]
 [28 40]] 

[[10 13]
 [28 40]] 

[[10 13]
 [28 40]] 

------------------------
[[ 0  1  4]
 [ 9 16 25]] 

[[0 3]
 [1 4]
 [2 5]] 

In [396]:
a = np.ones((256, 128, 3, 300))
print(a.shape, '\n')
print(a.transpose(3, 0, 1, 2).shape) # lub a.transpose(-1, 0, 1, 2).shape #Uwaga: "-1, -2.." to są indeksy od pońca
                                                                          # czyli -1 == 3, -2 == 2, -3 == 1, -4 == 0
(256, 128, 3, 300) 

(300, 256, 128, 3)

Inne użyteczne funkcje:

In [397]:
a = np.arange(6).reshape(3,2)
print(a, '\n')
print(np.exp(a), '\n') # e^x dla każdego elementu z a
print(np.sin(a), '\n') # sin(x) dla każdego elementu z a
print(np.sum(a), '\n') # suma tablicy
print(np.sum(a, axis=1), '\n') # suma wierszy
print(np.sum(a, axis=0), '\n') # suma kolumn
[[0 1]
 [2 3]
 [4 5]] 

[[  1.           2.71828183]
 [  7.3890561   20.08553692]
 [ 54.59815003 148.4131591 ]] 

[[ 0.          0.84147098]
 [ 0.90929743  0.14112001]
 [-0.7568025  -0.95892427]] 

15 

[1 5 9] 

[6 9] 

Przykład funkcja softmax i computationaly stable softmax

In [398]:
# problem tej funkcji polega na tym że kiedy zrobimy operacje np.exp na dużych liczbach ona może przyjąć
# wartość np.inf, czyli bardzo duża liczba np.  np.exp(1000), lub np.nan(not a number) jak podzielimy coś przez np.inf
# print(np.exp(1000), '\n')
def softmax(a):
    a_exp = np.exp(a)
    softmax = a_exp / np.sum(a_exp, axis=1, keepdims=True) # lub
    # softmax = a_exp / np.sum(a_exp, axis=1).reshape(-1, 1)
    print(softmax, '\n')

def stable_softmax(a):
    a_exp = np.exp( a - np.max(a, axis=1, keepdims=True) ) # lub
    # a_exp = np.exp( a - np.max(a, axis=1).reshape(-1,1))
    softmax = a_exp / np.sum(a_exp, axis=1, keepdims=True) #lub
    # softmax = a_exp / np.sum(a_exp, axis=1).reshape(-1, 1)
    print(softmax, '\n')
    

a = np.array([[1000, 1001], [1,2]])
softmax(a)
stable_softmax(a)
[[       nan        nan]
 [0.26894142 0.73105858]] 

[[0.26894142 0.73105858]
 [0.26894142 0.73105858]] 

/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:5: RuntimeWarning: overflow encountered in exp
  """
/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:6: RuntimeWarning: invalid value encountered in true_divide