44 KiB
Uczenie maszynowe — laboratoria
1. Podstawowe narzędzia uczenia maszynowego
1.1. Elementy języka Python przydatne w uczeniu maszynowym
Listy składane (_List comprehension)
Przypuśćmy, że mamy dane zdanie i chcemy utworzyć listę, która będzie zawierać długości kolejnych wyrazów tego zdania. Możemy to zrobić w następujący sposób:
zdanie = "tracz tarł tarcicę tak takt w takt jak takt w takt tarcicę tartak tarł"
wyrazy = zdanie.split()
dlugosci_wyrazow = []
for wyraz in wyrazy:
dlugosci_wyrazow.append(len(wyraz))
print(dlugosci_wyrazow)
[5, 4, 7, 3, 4, 1, 4, 3, 4, 1, 4, 7, 6, 4]
Możemy to też zrobić bardziej „pythonicznie”, przy użyciu list składanych:
zdanie = "tracz tarł tarcicę tak takt w takt jak takt w takt tarcicę tartak tarł"
dlugosci_wyrazow = [len(wyraz) for wyraz in zdanie.split()]
print(dlugosci_wyrazow)
[5, 4, 7, 3, 4, 1, 4, 3, 4, 1, 4, 7, 6, 4]
Jeżeli chcemy, żeby był sprawdzany dodatkowy warunek, np. chcemy pomijać wyraz „takt”, to wciąż możemy użyć list składanych:
zdanie = "tracz tarł tarcicę tak takt w takt jak takt w takt tarcicę tartak tarł"
wyrazy = zdanie.split()
# Ta konstrukcja:
dlugosci_wyrazow = []
for wyraz in wyrazy:
if wyraz != "takt":
dlugosci_wyrazow.append(wyraz)
# ...jest równoważna tej jednolinijkowej:
dlugosci_wyrazow = [len(wyraz) for wyraz in wyrazy if wyraz != "takt"]
print(dlugosci_wyrazow)
[5, 4, 7, 3, 1, 3, 1, 7, 6, 4]
Indeksowanie
Wszystkie listy i krotki w Pythonie, w tym łańcuchy (które trakowane są jak krotki znaków), są indeksowane od 0:
print(lista)
print(lista[:])
[4, 16, 36, 64, 100] [4, 16, 36, 64, 100]
napis = "abcde"
print(napis[0]) # 'a'
print(napis[4]) # 'e'
a e
Indeksy możemy liczyć również „od końca”:
napis = "abcde"
print(napis[-1]) # 'e' („ostatni”)
print(napis[-2]) # 'd' („drugi od końca”)
print(napis[-5]) # 'a' („piąty od końca”)
e d a
Łańcuchy możemy też „kroić na plasterki” (_slicing):
napis = "abcde"
print(napis[1:4]) # 'bcd' („znaki od 1. włącznie do 4. wyłącznie”)
print(napis[1:2]) # 'b' (to samo co `napis[1]`)
print(napis[-3:-1]) # 'cd' (kroić można też stosując indeksowanie od końca)
print(napis[1:-1]) # 'bcd' (możemy nawet mieszać te dwa sposoby indeksowania)
print(
napis[3:]
) # 'de' (jeżeli koniec przedziału nie jest podany, to kroimy do samego końca łańcucha)
print(
napis[:3]
) # 'abc' (jeżeli początek przedziału nie jest podany, to kroimy od początku łańcucha)
print(napis[:]) # 'abcde' (kopia całego napisu)
bcd b cd bcd de abc abcde
1.2. Biblioteka _NumPy
Tablice
Głównym obiektem w NumPy jest jednorodna, wielowymiarowa tablica. Przykładem takiej tablicy jest macierz x
.
Macierz $x = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{pmatrix}$ można zapisać jako:
import numpy as np
x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [2, 5, 8]])
print(x)
[[1 2 3] [4 5 6] [7 8 9] [2 5 8]]
Najczęsciej używane metody tablic typu array
:
print(x.shape) # wymiary macierzy
(4, 3)
print(x.sum(axis=0)) # suma liczb w każdej kolumnie
print(x.sum(axis=1)) # suma liczb w każdym wierszu
[14 20 26] [ 6 15 24 15]
print(x.mean(axis=1)) # średnia liczb w każdym wierszu
[2. 5. 8. 5.]
Do tworzenia sekwencji liczbowych jako obiekty typu array
należy wykorzystać funkcję arange
.
np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.arange(5, 10)
array([5, 6, 7, 8, 9])
np.arange(5, 10, 0.5)
array([5. , 5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])
Kształt tablicy można zmienić za pomocą metody reshape
:
x = np.arange(1, 13)
print(x)
y = x.reshape(3, 4)
print(y)
[ 1 2 3 4 5 6 7 8 9 10 11 12] [[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]]
Funkcją podobną do arange
jest linspace
, która wypełnia wektor określoną liczbą elementów z przedziału o równych automatycznie obliczonych odstępach (w arange
należy podać rozmiar kroku):
x = np.linspace(0, 5, 9)
print(x)
[0. 0.625 1.25 1.875 2.5 3.125 3.75 4.375 5. ]
Dodatkowe informacje o funkcjach NumPy uzyskuje się za pomocą polecenia help(nazwa_funkcji)
:
help(np.shape)
Help on function shape in module numpy: shape(a) Return the shape of an array. Parameters ---------- a : array_like Input array. Returns ------- shape : tuple of ints The elements of the shape tuple give the lengths of the corresponding array dimensions. See Also -------- alen ndarray.shape : Equivalent array method. Examples -------- >>> np.shape(np.eye(3)) (3, 3) >>> np.shape([[1, 2]]) (1, 2) >>> np.shape([0]) (1,) >>> np.shape(0) () >>> a = np.array([(1, 2), (3, 4)], dtype=[('x', 'i4'), ('y', 'i4')]) >>> np.shape(a) (2,) >>> a.shape (2,)
Tablice mogą składać się z danych różnych typów (ale tylko jednego typu danych równocześnie, stąd jednorodność).
x = np.array([1, 2, 3])
print(x, "- typ: ", x.dtype)
x = np.array([0.1, 0.2, 0.3])
print(x, "- typ: ", x.dtype)
x = np.array([1, 2, 3], dtype="float64")
print(x, "- typ: ", x.dtype)
[1 2 3] - typ: int32 [0.1 0.2 0.3] - typ: float64 [1. 2. 3.] - typ: float64
Tworzenie tablic składających się z samych zer lub jedynek umożliwiają funkcje zeros
oraz ones
:
x = np.zeros([3, 4])
print(x)
[[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]]
x = np.ones([3, 4])
print(x)
[[1. 1. 1. 1.] [1. 1. 1. 1.] [1. 1. 1. 1.]]
Podstawowe operacje arytmetyczne
Operatory arytmetyczne na tablicach w NumPy działają element po elemencie.
import numpy as np
a = np.array([3, 4, 5])
print(a)
b = np.ones(3)
print(b)
print(a / b)
[3 4 5] [1. 1. 1.] [3. 4. 5.]
Za mnożenie macierzy odpowiadają funkcje dot
i matmul
(nie operator *
):
a = np.array([[1, 2], [3, 4]])
print(a)
[[1 2] [3 4]]
b = np.array([[1, 2], [3, 4]])
print(b)
[[1 2] [3 4]]
a * b # mnożenie element po elemencie
array([[ 7, 10], [15, 22]])
a @ b # mnożenie macierzowe
array([[ 7, 10], [15, 22]])
np.dot(a, b) # mnożenie macierzowe
array([[ 7, 10], [15, 22]])
np.matmul(a, b) # mnożenie macierzowe
array([[ 7, 10], [15, 22]])
Przykłady innych operacji dodawania i mnożenia:
a = np.zeros((2, 2), dtype="float")
a += 5
a
array([[5., 5.], [5., 5.]])
a *= 5
a
array([[25., 25.], [25., 25.]])
a + a
array([[50., 50.], [50., 50.]])
Sklejanie tablic:
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.array([7, 8, 9])
np.hstack([a, b, c])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
np.vstack([a, b, c])
array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Typowe funkcje matematyczne:
x = np.arange(1, 5)
np.sqrt(x) * np.pi
array([3.14159265, 4.44288294, 5.44139809, 6.28318531])
2**4
16
np.power(2, 4)
16
np.log(np.e)
1.0
x = np.arange(5)
x.max() - x.min()
4
Indeksy i zakresy
Tablice jednowymiarowe zachowują sie podobnie do zwykłych list pythonowych.
a = np.arange(10)
a[2:4]
array([2, 3])
a[:10:2]
array([0, 2, 4, 6, 8])
a[::-1]
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
Tablice wielowymiarowe mają po jednym indeksie na wymiar:
x = np.arange(12).reshape(3, 4)
x
array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
x[2, 3]
11
x[:, 1] # kolumna nr 1
array([1, 5, 9])
x[1, :] # wiersz nr 1
array([4, 5, 6, 7])
x[1:3, :]
array([[ 4, 5, 6, 7], [ 8, 9, 10, 11]])
Warunki
Warunki pozwalają na selekcję elementów tablicy.
a = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3])
a[a > 1]
array([2, 2, 2, 3, 3, 3])
a[a == 3]
array([3, 3, 3])
np.where(a < 3)
(array([0, 1, 2, 3, 4, 5], dtype=int64),)
np.where(a < 3)[0]
array([0, 1, 2, 3, 4, 5], dtype=int64)
np.where(a > 9)
(array([], dtype=int64),)
Pętle i wypisywanie
for row in x:
print(row)
[0 1 2 3] [4 5 6 7] [ 8 9 10 11]
for element in x.flat:
print(element)
0 1 2 3 4 5 6 7 8 9 10 11
Liczby losowe
np.random.randint(0, 10, 5)
array([1, 3, 3, 1, 2])
np.random.normal(0, 1, 5)
array([ 2.25701199, -0.62666283, -0.58260693, 0.91053811, -0.12398967])
np.random.uniform(0, 2, 5)
array([0.64188687, 1.98379682, 0.4690363 , 1.26967692, 0.84376779])
Macierze
NumPy jest pakietem wykorzystywanym do obliczeń w dziedzinie algebry liniowej, co jeszcze szczególnie przydatne w uczeniu maszynowym.
Wektor o wymiarach $1 \times N$ $$ x = \begin{pmatrix} x_{1} \\ x_{2} \\ \vdots \\ x_{N} \end{pmatrix} $$
i jego transpozycję $x^\top = (x_{1}, x_{2},\ldots,x_{N})$ można wyrazić w Pythonie w następujący sposób:
import numpy as np
x = np.array([[1, 2, 3]]).T
x.shape
(3, 1)
xt = x.T
xt.shape
(1, 3)
Macierz kolumnowa w NumPy.
$$X =
\begin{pmatrix}
3 \\
4 \\
5 \\
6
\end{pmatrix}$$
x = np.array([[3, 4, 5, 6]]).T
x
array([[3], [4], [5], [6]])
Macierz wierszowa w NumPy. $$ X = \begin{pmatrix} 3 & 4 & 5 & 6 \end{pmatrix}$$
x = np.array([[3, 4, 5, 6]])
x
array([[3, 4, 5, 6]])
Oprócz obiektów typu array
istnieje wyspecjalizowany obiekt matrix
, dla którego operacje *
(mnożenie) oraz **-1
(odwracanie) są określone w sposób właściwy dla macierzy (w przeciwieństwie do operacji elementowych dla obiektów array
).
x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]).reshape(3, 3)
print(x)
[[1 2 3] [4 5 6] [7 8 9]]
y = np.array([4, 6, 3, 8, 7, 1, 3, 0, 3]).reshape(3, 3)
print(y)
[[4 6 3] [8 7 1] [3 0 3]]
X = np.matrix(x)
Y = np.matrix(y)
print(x * y) # Tablice np.array mnożone są element po elemencie
[[ 4 12 9] [32 35 6] [21 0 27]]
print(X * Y) # Macierze np.matrix mnożone są macierzowo
[[ 29 20 14] [ 74 59 35] [119 98 56]]
print(np.matmul(x, y))
[[ 29 20 14] [ 74 59 35] [119 98 56]]
Wyznacznik macierzy
a = np.array([[3, -9], [2, 5]])
np.linalg.det(a)
33.000000000000014
Macierz odwrotna
A = np.array([[-4, -2], [5, 5]])
A
array([[-4, -2], [ 5, 5]])
invA = np.linalg.inv(A)
invA
array([[-0.5, -0.2], [ 0.5, 0.4]])
np.round(np.dot(A, invA))
array([[1., 0.], [0., 1.]])
(ponieważ $AA^{-1} = A^{-1}A = I$).
Wartości i wektory własne
a = np.diag((1, 2, 3))
a
array([[1, 0, 0], [0, 2, 0], [0, 0, 3]])
w, v = np.linalg.eig(a)
print(w) # wartości własne
print(v) # wektory własne
[1. 2. 3.] [[1. 0. 0.] [0. 1. 0.] [0. 0. 1.]]
1.3. Biblioteka PyTorch
Biblioteka PyTorch została stworzona z myślą o uczeniu maszynowym. Oprócz wykonywania rozmaitych działań matematycznych takich jak te, które można wykonywać w bibliotece NumPy, dostarcza metod przydatnych w uczeniu maszynowym, z których chyba najbardziej charakterystyczną jest automatyczne różniczkowanie (moduł autograd
).
Ale o tym później.
Instalacja
pip install torch torchvision
lub
conda install pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch
Tensory
Podstawowym typem danych dla pakietu pytorch
jest tensor (torch.tensor
). Tensor to uogólnienie macierzy na dowolną liczbę wymiarów. Można powiedzieć, że macierze są dwuwymiarowymi tensorami.
import torch
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(x)
tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Operacje na tensorach
Działania na tensorach w bibliotece PyTorch wykonuje się bardzo podobnie do działań na miacierzach w bibliotece NumPy. Czasami nazwy metod się trochę różnią.
# Wymiary (rozmiar) tensora
print(x.shape)
print(x.size()) # Można użyć `size()` zamiast `shape`
torch.Size([3, 3]) torch.Size([3, 3])
# Typy elementów
x = torch.tensor([1, 2, 3])
print(x, "- type:", x.dtype)
x = torch.tensor([0.1, 0.2, 0.3])
print(x, "- type:", x.dtype)
x = torch.tensor([1, 2, 3], dtype=torch.float64) # Uwaga: inaczej niż w NumPy
print(x, "- type:", x.dtype)
tensor([1, 2, 3]) - type: torch.int64 tensor([0.1000, 0.2000, 0.3000]) - type: torch.float32 tensor([1., 2., 3.], dtype=torch.float64) - type: torch.float64
x = torch.zeros([3, 4])
print(x)
tensor([[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]])
x = torch.ones([3, 4])
print(x)
tensor([[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]])
x = torch.rand([3, 4])
print(x)
tensor([[0.9863, 0.9173, 0.5301, 0.4279], [0.7708, 0.4671, 0.2965, 0.0578], [0.6684, 0.4432, 0.9817, 0.1521]])
# Iterowanie po elementach tensora
for i, row in enumerate(x):
print(f"\nWiersz {i}:")
for element in row:
print(
element.item()
) # `item()` zamienia jednoelementowy (bezwymiarowy) tensor na liczbę
Wiersz 0: 0.9863188862800598 0.917273998260498 0.53009432554245 0.42788761854171753 Wiersz 1: 0.7708230018615723 0.46713775396347046 0.2964947819709778 0.057803571224212646 Wiersz 2: 0.6684107780456543 0.4432327151298523 0.9817106127738953 0.15205740928649902
# Przykładowe macierze
A = torch.rand([3, 4])
print(A)
B = torch.rand([3, 4])
print(B)
C = torch.rand([4, 2])
print(C)
tensor([[0.1572, 0.4592, 0.7481, 0.6673], [0.1138, 0.9820, 0.4452, 0.5775], [0.7510, 0.3174, 0.6937, 0.8904]]) tensor([[0.0661, 0.6596, 0.7498, 0.5254], [0.3271, 0.8968, 0.3188, 0.9255], [0.2099, 0.5828, 0.4611, 0.6856]]) tensor([[0.0738, 0.3776], [0.2646, 0.5449], [0.6779, 0.0567], [0.0348, 0.3072]])
# Działania "element po elemencie"
print(A + B)
print(A - B)
print(A * B)
print(A / B)
tensor([[0.2232, 1.1188, 1.4978, 1.1928], [0.4409, 1.8788, 0.7640, 1.5029], [0.9609, 0.9002, 1.1548, 1.5760]]) tensor([[ 0.0911, -0.2005, -0.0017, 0.1419], [-0.2133, 0.0853, 0.1264, -0.3480], [ 0.5410, -0.2654, 0.2326, 0.2048]]) tensor([[0.0104, 0.3029, 0.5609, 0.3506], [0.0372, 0.8807, 0.1419, 0.5344], [0.1577, 0.1850, 0.3199, 0.6105]]) tensor([[2.3777, 0.6961, 0.9977, 1.2701], [0.3478, 1.0951, 1.3966, 0.6240], [3.5770, 0.5446, 1.5044, 1.2988]])
# Mnożenie macierzowe
print(torch.matmul(A, C))
tensor([[0.6635, 0.5570], [0.5902, 0.7807], [0.6406, 0.7694]])
Konwersja między PyTorch i NumPy
# Konwersja z PyTorch do NumPy
print(A)
A_numpy = A.numpy()
print(A_numpy)
tensor([[0.1572, 0.4592, 0.7481, 0.6673], [0.1138, 0.9820, 0.4452, 0.5775], [0.7510, 0.3174, 0.6937, 0.8904]]) [[0.15715027 0.45915365 0.7480644 0.66733134] [0.11377418 0.98203135 0.4451999 0.5774748 ] [0.7509776 0.3174067 0.69367564 0.8904279 ]]
# Konwersja z numpy do PyTorch
X = np.random.rand(3, 5)
print(X)
X_pytorch = torch.from_numpy(X)
print(X_pytorch)
[[0.84580006 0.49270934 0.67969751 0.27546956 0.10600392] [0.84610871 0.11680263 0.3535065 0.83725955 0.07995571] [0.4586334 0.64818257 0.53201793 0.77786372 0.8584107 ]] tensor([[0.8458, 0.4927, 0.6797, 0.2755, 0.1060], [0.8461, 0.1168, 0.3535, 0.8373, 0.0800], [0.4586, 0.6482, 0.5320, 0.7779, 0.8584]], dtype=torch.float64)
Przydatne materiały
- NumPy - dokumentacja: https://numpy.org/doc/stable
- PyTorch - dokumentacja: https://pytorch.org/docs/stable