====== Algebră Liniară ====== Până acum, putem încărca seturi de date în tensori și putem manipula acești tensori cu operații matematice de bază. Pentru a începe să construim modele sofisticate, vom avea nevoie și de câteva instrumente din algebra liniară. Această secțiune oferă o introducere ușoară în cele mai esențiale concepte, pornind de la aritmetica scalară și urcând până la înmulțirea matricelor. #tab mxnet from mxnet import np, npx npx.set_np() #tab pytorch import torch #tab tensorflow import tensorflow as tf #tab jax from jax import numpy as jnp ===== Scalari ===== Majoritatea matematicii de zi cu zi constă în manipularea numerelor unul câte unul. Formal, numim aceste valori //scalari//. De exemplu, temperatura în Palo Alto este de $72$ grade Fahrenheit. Dacă ați dori să convertiți temperatura în Celsius, ați evalua expresia $c = \frac{5}{9}(f - 32)$, setând $f$ la $72$. În această ecuație, valorile $5$, $9$ și $32$ sunt scalari constanți. Variabilele $c$ și $f$ reprezintă în general scalari necunoscuți. Notăm scalarii prin litere mici obișnuite (de exemplu, $x$, $y$ și $z$) și spațiul tuturor scalarilor //cu valoare reală// (continui) prin $\mathbb{R}$. Pentru operativitate, vom sări peste definițiile riguroase ale //spațiilor//: nu uitați că expresia $x \in \mathbb{R}$ este un mod formal de a spune că $x$ este un scalar cu valoare reală. Simbolul $\in$ (pronunțat „în”) denotă apartenența la o mulțime. De exemplu, $x, y \in \{0, 1\}$ indică faptul că $x$ și $y$ sunt variabile care pot lua doar valorile $0$ sau $1$. (**Scalarii sunt implementați ca tensori care conțin doar un singur element.**) Mai jos, atribuim doi scalari și efectuăm operațiile familiare de adunare, înmulțire, împărțire și exponențiere. #tab mxnet x = np.array(3.0) y = np.array(2.0) x + y, x * y, x / y, x ** y #tab pytorch x = torch.tensor(3.0) y = torch.tensor(2.0) x + y, x * y, x / y, x**y #tab tensorflow x = tf.constant(3.0) y = tf.constant(2.0) x + y, x * y, x / y, x**y #tab jax x = jnp.array(3.0) y = jnp.array(2.0) x + y, x * y, x / y, x**y ===== Vectori ===== În scopurile actuale, [**vă puteți gândi la un vector ca la un tablou de scalari cu lungime fixă.**] La fel ca în cazul omologilor lor de cod, numim acești scalari //elementele// vectorului (sinonimele includ //intrări// și //componente//). Atunci când vectorii reprezintă exemple din seturi de date din lumea reală, valorile lor au o semnificație în lumea reală. De exemplu, dacă antrenăm un model pentru a prezice riscul de neplată a unui împrumut, am putea asocia fiecărui solicitant un vector ale cărui componente corespund unor cantități precum venitul acestora, durata angajării sau numărul de neplăți anterioare. Dacă studiem riscul de atac de cord, fiecare vector ar putea reprezenta un pacient, iar componentele sale ar putea corespunde celor mai recente semne vitale, nivelurilor de colesterol, minutelor de exercițiu pe zi etc. Notăm vectorii prin litere mici îngroșate (de exemplu, $\mathbf{x}$, $\mathbf{y}$ și $\mathbf{z}$). Vectorii sunt implementați ca tensori de ordinul $1$. În general, astfel de tensori pot avea lungimi arbitrare, sub rezerva limitărilor de memorie. Atenție: în Python, ca în majoritatea limbajelor de programare, indicii vectoriali încep de la $0$, cunoscută și sub numele de //indexare bazată pe zero//, în timp ce în algebra liniară indicii încep de la $1$ (indexare bazată pe unu). #tab mxnet x = np.arange(3) x #tab pytorch x = torch.arange(3) x #tab tensorflow x = tf.range(3) x #tab jax x = jnp.arange(3) x Ne putem referi la un element al unui vector folosind un indice. De exemplu, $x_2$ denotă al doilea element al lui $\mathbf{x}$. Deoarece $x_2$ este un scalar, nu îl îngroșăm. În mod implicit, vizualizăm vectorii stivuind elementele lor vertical: $$\mathbf{x} =\begin{bmatrix}x_{1} \\ \vdots \\x_{n}\end{bmatrix}.$$ Aici $x_1, \ldots, x_n$ sunt elemente ale vectorului. Mai târziu, vom face distincția între astfel de //vectori coloană// și //vectori rând// ale căror elemente sunt stivuite orizontal. Amintiți-vă că [**accesăm elementele unui tensor prin indexare.**] #tab all x[2] Pentru a indica faptul că un vector conține $n$ elemente, scriem $\mathbf{x} \in \mathbb{R}^n$. Formal, numim $n$ //dimensionalitatea// vectorului. [**În cod, aceasta corespunde lungimii tensorului**], accesibilă prin funcția încorporată ''#len#'' din Python. #tab all len(x) De asemenea, putem accesa lungimea prin atributul ''#shape#''. Forma este un tuplu care indică lungimea unui tensor de-a lungul fiecărei axe. (**Tensorii cu o singură axă au forme cu un singur element.**) #tab all x.shape Adesea, cuvântul „dimensiune” este supraîncărcat pentru a însemna atât numărul de axe, cât și lungimea de-a lungul unei anumite axe. Pentru a evita această confuzie, folosim //ordin// pentru a ne referi la numărul de axe și //dimensionalitate// exclusiv pentru a ne referi la numărul de componente. ===== Matrici ===== La fel cum scalarii sunt tensori de ordinul $0$ și vectorii sunt tensori de ordinul $1$, matricile sunt tensori de ordinul $2$. Notăm matricile cu litere majuscule (de exemplu, $\mathbf{X}$, $\mathbf{Y}$ și $\mathbf{Z}$) și le reprezentăm în cod prin tensori cu două axe. Expresia $\mathbf{A} \in \mathbb{R}^{m \times n}$ indică faptul că o matrice $\mathbf{A}$ conține $m \times n$ scalari cu valoare reală, aranjați ca $m$ rânduri și $n$ coloane. Când $m = n$, spunem că o matrice este //pătratică//. Vizual, putem ilustra orice matrice ca un tabel. Pentru a ne referi la un element individual, scriem ca indice atât rândul cât și coloana, de exemplu, $a_{ij}$ este valoarea care aparține rândului $i$ și coloanei $j$ a lui $\mathbf{A}$: $$\mathbf{A}=\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \\ \end{bmatrix}.$$ În cod, reprezentăm o matrice $\mathbf{A} \in \mathbb{R}^{m \times n}$ printr-un tensor de ordinul $2$ cu forma ($m$, $n$). [**Putem converti orice tensor de dimensiune corespunzătoare $m \times n$ într-o matrice $m \times n$**] trecând forma dorită la ''#reshape#'': #tab mxnet A = np.arange(6).reshape(3, 2) A #tab pytorch A = torch.arange(6).reshape(3, 2) A #tab tensorflow A = tf.reshape(tf.range(6), (3, 2)) A #tab jax A = jnp.arange(6).reshape(3, 2) A Uneori vrem să răsturnăm axele. Când schimbăm rândurile și coloanele unei matrice, rezultatul se numește //transpusa// sa. Formal, semnificăm transpusa unei matrice $\mathbf{A}$ prin $\mathbf{A}^\top$ și dacă $\mathbf{B} = \mathbf{A}^\top$, atunci $b_{ij} = a_{ji}$ pentru toți $i$ și $j$. Astfel, transpusa unei matrice $m \times n$ este o matrice $n \times m$: $$ \mathbf{A}^\top = \begin{bmatrix} a_{11} & a_{21} & \dots & a_{m1} \\ a_{12} & a_{22} & \dots & a_{m2} \\ \vdots & \vdots & \ddots & \vdots \\ a_{1n} & a_{2n} & \dots & a_{mn} \end{bmatrix}. $$ În cod, putem accesa orice (**transpusă a unei matrice**) după cum urmează: #tab mxnet, pytorch, jax A.T #tab tensorflow tf.transpose(A) [**Matricele simetrice sunt submulțimea matricelor pătratice care sunt egale cu propriile lor transpuse: $\mathbf{A} = \mathbf{A}^\top$.**] Următoarea matrice este simetrică: #tab mxnet A = np.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) A == A.T #tab pytorch A = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) A == A.T #tab tensorflow A = tf.constant([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) A == tf.transpose(A) #tab jax A = jnp.array([[1, 2, 3], [2, 0, 4], [3, 4, 5]]) A == A.T Matricele sunt utile pentru reprezentarea seturilor de date. De obicei, rândurile corespund înregistrărilor individuale, iar coloanele corespund atributelor distincte. ===== Tensori ===== Deși puteți merge departe în călătoria de învățare automată doar cu scalari, vectori și matrice, în cele din urmă poate fi necesar să lucrați cu [**tensori**] de ordin superior. Tensorii (**ne oferă o modalitate generică de a descrie extensii la tablouri de ordinul $n$.**) Numim obiectele software din //clasa tensor// „tensori” tocmai pentru că și ele pot avea numere arbitrare de axe. Deși poate fi confuz să folosim cuvântul //tensor// atât pentru obiectul matematic, cât și pentru realizarea sa în cod, sensul nostru ar trebui să fie de obicei clar din context. Notăm tensorii generali cu majuscule cu un font special (de exemplu, $\mathsf{X}$, $\mathsf{Y}$ și $\mathsf{Z}$), iar mecanismul lor de indexare (de exemplu, $x_{ijk}$ și $[\mathsf{X}]_{1, 2i-1, 3}$) decurge natural din cel al matricelor. Tensorii vor deveni mai importanți atunci când vom începe să lucrăm cu imagini. Fiecare imagine ajunge ca un tensor de ordinul $3$ cu axe corespunzătoare înălțimii, lățimii și //canalului//. La fiecare locație spațială, intensitățile fiecărei culori (roșu, verde și albastru) sunt stivuite de-a lungul canalului. Mai mult, o colecție de imagini este reprezentată în cod printr-un tensor de ordinul $4$, unde imaginile distincte sunt indexate de-a lungul primei axe. Tensorii de ordin superior sunt construiți, la fel ca vectorii și matricele, prin creșterea numărului de componente de formă. #tab mxnet np.arange(24).reshape(2, 3, 4) #tab pytorch torch.arange(24).reshape(2, 3, 4) #tab tensorflow tf.reshape(tf.range(24), (2, 3, 4)) #tab jax jnp.arange(24).reshape(2, 3, 4) ===== Proprietăți de Bază ale Aritmeticii Tensorilor ===== Scalarii, vectorii, matricele și tensorii de ordin superior au toate câteva proprietăți utile. De exemplu, operațiile element cu element produc ieșiri care au aceeași formă ca operanzii lor. #tab mxnet A = np.arange(6).reshape(2, 3) B = A.copy() # Assign a copy of A to B by allocating new memory A, A + B #tab pytorch A = torch.arange(6, dtype=torch.float32).reshape(2, 3) B = A.clone() # Assign a copy of A to B by allocating new memory A, A + B #tab tensorflow A = tf.reshape(tf.range(6, dtype=tf.float32), (2, 3)) B = A # No cloning of A to B by allocating new memory A, A + B #tab jax A = jnp.arange(6, dtype=jnp.float32).reshape(2, 3) B = A A, A + B [**Produsul element cu element a două matrice se numește //produsul Hadamard//**] (notat $\odot$). Putem preciza intrările produsului Hadamard a două matrice $\mathbf{A}, \mathbf{B} \in \mathbb{R}^{m \times n}$: $$ \mathbf{A} \odot \mathbf{B} = \begin{bmatrix} a_{11} b_{11} & a_{12} b_{12} & \dots & a_{1n} b_{1n} \\ a_{21} b_{21} & a_{22} b_{22} & \dots & a_{2n} b_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} b_{m1} & a_{m2} b_{m2} & \dots & a_{mn} b_{mn} \end{bmatrix}. $$ #tab all A * B [**Adunarea sau înmulțirea unui scalar și a unui tensor**] produce un rezultat cu aceeași formă ca tensorul original. Aici, fiecare element al tensorului este adăugat la (sau înmulțit cu) scalar. #tab mxnet a = 2 X = np.arange(24).reshape(2, 3, 4) a + X, (a * X).shape #tab pytorch a = 2 X = torch.arange(24).reshape(2, 3, 4) a + X, (a * X).shape #tab tensorflow a = 2 X = tf.reshape(tf.range(24), (2, 3, 4)) a + X, (a * X).shape #tab jax a = 2 X = jnp.arange(24).reshape(2, 3, 4) a + X, (a * X).shape ===== Reducere ===== Adesea, dorim să calculăm [**suma elementelor unui tensor.**] Pentru a exprima suma elementelor într-un vector $\mathbf{x}$ de lungime $n$, scriem $\sum_{i=1}^n x_i$. Există o funcție simplă pentru aceasta: #tab mxnet x = np.arange(3) x, x.sum() #tab pytorch x = torch.arange(3, dtype=torch.float32) x, x.sum() #tab tensorflow x = tf.range(3, dtype=tf.float32) x, tf.reduce_sum(x) #tab jax x = jnp.arange(3, dtype=jnp.float32) x, x.sum() Pentru a exprima [**sume peste elementele tensorilor de formă arbitrară**], pur și simplu însumăm peste toate axele sale. De exemplu, suma elementelor unei matrice $m \times n$ $\mathbf{A}$ ar putea fi scrisă $\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}$. #tab mxnet, pytorch, jax A.shape, A.sum() #tab tensorflow A.shape, tf.reduce_sum(A) Implicit, invocarea funcției sum //reduce// un tensor de-a lungul tuturor axelor sale, producând în cele din urmă un scalar. Bibliotecile noastre ne permit, de asemenea, să [**specificăm axele de-a lungul cărora tensorul ar trebui redus.**] Pentru a însuma toate elementele de-a lungul rândurilor (axa 0), specificăm ''#axis=0#'' în ''#sum#''. Deoarece matricea de intrare se reduce de-a lungul axei 0 pentru a genera vectorul de ieșire, această axă lipsește din forma ieșirii. #tab mxnet, pytorch, jax A.shape, A.sum(axis=0).shape #tab tensorflow A.shape, tf.reduce_sum(A, axis=0).shape Specificarea ''#axis=1#'' va reduce dimensiunea coloanei (axa 1) prin însumarea elementelor tuturor coloanelor. #tab mxnet, pytorch, jax A.shape, A.sum(axis=1).shape #tab tensorflow A.shape, tf.reduce_sum(A, axis=1).shape Reducerea unei matrice atât de-a lungul rândurilor, cât și al coloanelor prin însumare este echivalentă cu însumarea tuturor elementelor matricei. #tab mxnet, pytorch, jax A.sum(axis=[0, 1]) == A.sum() # Same as A.sum() #tab tensorflow tf.reduce_sum(A, axis=[0, 1]), tf.reduce_sum(A) # Same as tf.reduce_sum(A) [**O cantitate înrudită este //media//, numită și //medie// (average).**] Calculăm media împărțind suma la numărul total de elemente. Deoarece calcularea mediei este atât de comună, primește o funcție de bibliotecă dedicată care funcționează analog cu ''#sum#''. #tab mxnet, jax A.mean(), A.sum() / A.size #tab pytorch A.mean(), A.sum() / A.numel() #tab tensorflow tf.reduce_mean(A), tf.reduce_sum(A) / tf.size(A).numpy() La fel, funcția pentru calcularea mediei poate reduce și un tensor de-a lungul unor axe specifice. #tab mxnet, pytorch, jax A.mean(axis=0), A.sum(axis=0) / A.shape[0] #tab tensorflow tf.reduce_mean(A, axis=0), tf.reduce_sum(A, axis=0) / A.shape[0] ===== Sumă Fără Reducere ===== Uneori poate fi util să [**păstrăm numărul de axe neschimbat**] atunci când invocăm funcția pentru calcularea sumei sau a mediei. Acest lucru contează atunci când vrem să folosim mecanismul de broadcast. #tab mxnet, pytorch, jax sum_A = A.sum(axis=1, keepdims=True) sum_A, sum_A.shape #tab tensorflow sum_A = tf.reduce_sum(A, axis=1, keepdims=True) sum_A, sum_A.shape De exemplu, deoarece ''#sum_A#'' își păstrează cele două axe după însumarea fiecărui rând, putem (**împărți ''#A#'' la ''#sum_A#'' cu broadcasting**) pentru a crea o matrice în care fiecare rând se însumează la $1$. #tab all A / sum_A Dacă vrem să calculăm [**suma cumulativă a elementelor lui ''#A#'' de-a lungul unei axe**], să spunem ''#axis=0#'' (rând cu rând), putem apela funcția ''#cumsum#''. Prin proiectare, această funcție nu reduce tensorul de intrare de-a lungul niciunei axe. #tab mxnet, pytorch, jax A.cumsum(axis=0) #tab tensorflow tf.cumsum(A, axis=0) ===== Produse Scalare ===== Până acum, am efectuat doar operații element cu element, sume și medii. Și dacă asta ar fi tot ce am putea face, algebra liniară nu și-ar merita propria secțiune. Din fericire, aici lucrurile devin mai interesante. Una dintre cele mai fundamentale operații este produsul scalar. Date fiind doi vectori $\mathbf{x}, \mathbf{y} \in \mathbb{R}^d$, //produsul lor scalar// $\mathbf{x}^\top \mathbf{y}$ (cunoscut și sub numele de //produs intern//, $\langle \mathbf{x}, \mathbf{y} \rangle$) este o sumă peste produsele elementelor de la aceeași poziție: $\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i$. [The //dot product// of two vectors is a sum over the products of the elements at the same position] #tab mxnet y = np.ones(3) x, y, np.dot(x, y) #tab pytorch y = torch.ones(3, dtype = torch.float32) x, y, torch.dot(x, y) #tab tensorflow y = tf.ones(3, dtype=tf.float32) x, y, tf.tensordot(x, y, axes=1) #tab jax y = jnp.ones(3, dtype = jnp.float32) x, y, jnp.dot(x, y) Echivalent, (**putem calcula produsul scalar a doi vectori efectuând o înmulțire element cu element urmată de o sumă:**) #tab mxnet np.sum(x * y) #tab pytorch torch.sum(x * y) #tab tensorflow tf.reduce_sum(x * y) #tab jax jnp.sum(x * y) Produsele scalare sunt utile într-o gamă largă de contexte. De exemplu, dat fiind un set de valori, notat printr-un vector $\mathbf{x} \in \mathbb{R}^n$, și un set de ponderi, notat prin $\mathbf{w} \in \mathbb{R}^n$, suma ponderată a valorilor din $\mathbf{x}$ conform ponderilor $\mathbf{w}$ ar putea fi exprimată ca produsul scalar $\mathbf{x}^\top \mathbf{w}$. Când ponderile sunt nenegative și se însumează la $1$, adică $\left(\sum_{i=1}^{n} {w_i} = 1\right)$, produsul scalar exprimă o //medie ponderată//. După normalizarea a doi vectori pentru a avea lungime unitară, produsele scalare exprimă cosinusul unghiului dintre ei. Mai târziu în această secțiune, vom introduce formal această noțiune de //lungime//. ===== Produse Matrice-Vector ===== Acum că știm cum să calculăm produse scalare, putem începe să înțelegem //produsul// dintre o matrice $m \times n$, $\mathbf{A}$, și un vector $n$-dimensional $\mathbf{x}$. Pentru a începe, vizualizăm matricea noastră în termeni de vectori rând ai săi $$\mathbf{A}= \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_m \\ \end{bmatrix},$$ unde fiecare $\mathbf{a}^\top_{i} \in \mathbb{R}^n$ este un vector rând reprezentând al $i$-lea rând al matricei $\mathbf{A}$. [**Produsul matrice-vector $\mathbf{A}\mathbf{x}$ este pur și simplu un vector coloană de lungime $m$, al cărui al $i$-lea element este produsul scalar $\mathbf{a}^\top_i \mathbf{x}$:**] $$ \mathbf{A}\mathbf{x} = \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_m \\ \end{bmatrix}\mathbf{x} = \begin{bmatrix} \mathbf{a}^\top_{1} \mathbf{x} \\ \mathbf{a}^\top_{2} \mathbf{x} \\ \vdots\\ \mathbf{a}^\top_{m} \mathbf{x}\\ \end{bmatrix}. $$ Ne putem gândi la înmulțirea cu o matrice $\mathbf{A}\in \mathbb{R}^{m \times n}$ ca la o transformare care proiectează vectori din $\mathbb{R}^{n}$ în $\mathbb{R}^{m}$. Aceste transformări sunt remarcabil de utile. De exemplu, putem reprezenta rotațiile ca înmulțiri cu anumite matrice pătratice. Produsele matrice-vector descriu, de asemenea, calculul cheie implicat în calcularea ieșirilor fiecărui strat într-o rețea neuronală, date fiind ieșirile din stratul anterior. Pentru a exprima un produs matrice-vector în cod, folosim funcția ''#mv#''. Rețineți că dimensiunea coloanei lui ''#A#'' (lungimea sa de-a lungul axei 1) trebuie să fie aceeași cu dimensiunea lui ''#x#'' (lungimea sa). Python are un operator de confort ''#@#'' care poate executa atât produse matrice-vector, cât și produse matrice-matrice (în funcție de argumentele sale). Astfel putem scrie ''#A@x#''. #tab mxnet A.shape, x.shape, np.dot(A, x) #tab pytorch A.shape, x.shape, torch.mv(A, x), A@x #tab tensorflow A.shape, x.shape, tf.linalg.matvec(A, x) #tab jax A.shape, x.shape, jnp.matmul(A, x) ===== Înmulțirea Matrice-Matrice ===== Odată ce ați prins ideea produselor scalare și a produselor matrice-vector, atunci //înmulțirea matrice-matrice// ar trebui să fie simplă. Să spunem că avem două matrice $\mathbf{A} \in \mathbb{R}^{n \times k}$ și $\mathbf{B} \in \mathbb{R}^{k \times m}$: $$\mathbf{A}=\begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1k} \\ a_{21} & a_{22} & \cdots & a_{2k} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nk} \\ \end{bmatrix},\quad \mathbf{B}=\begin{bmatrix} b_{11} & b_{12} & \cdots & b_{1m} \\ b_{21} & b_{22} & \cdots & b_{2m} \\ \vdots & \vdots & \ddots & \vdots \\ b_{k1} & b_{k2} & \cdots & b_{km} \\ \end{bmatrix}.$$ Fie $\mathbf{a}^\top_{i} \in \mathbb{R}^k$ vectorul rând reprezentând al $i$-lea rând al matricei $\mathbf{A}$ și fie $\mathbf{b}_{j} \in \mathbb{R}^k$ vectorul coloană din a $j$-a coloană a matricei $\mathbf{B}$: $$\mathbf{A}= \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_n \\ \end{bmatrix}, \quad \mathbf{B}=\begin{bmatrix} \mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\ \end{bmatrix}. $$ Pentru a forma produsul matrice $\mathbf{C} \in \mathbb{R}^{n \times m}$, calculăm pur și simplu fiecare element $c_{ij}$ ca produsul scalar dintre al $i$-lea rând al lui $\mathbf{A}$ și a $j$-a coloană a lui $\mathbf{B}$, i.e., $\mathbf{a}^\top_i \mathbf{b}_j$: $$\mathbf{C} = \mathbf{AB} = \begin{bmatrix} \mathbf{a}^\top_{1} \\ \mathbf{a}^\top_{2} \\ \vdots \\ \mathbf{a}^\top_n \\ \end{bmatrix} \begin{bmatrix} \mathbf{b}_{1} & \mathbf{b}_{2} & \cdots & \mathbf{b}_{m} \\ \end{bmatrix} = \begin{bmatrix} \mathbf{a}^\top_{1} \mathbf{b}_1 & \mathbf{a}^\top_{1}\mathbf{b}_2& \cdots & \mathbf{a}^\top_{1} \mathbf{b}_m \\ \mathbf{a}^\top_{2}\mathbf{b}_1 & \mathbf{a}^\top_{2} \mathbf{b}_2 & \cdots & \mathbf{a}^\top_{2} \mathbf{b}_m \\ \vdots & \vdots & \ddots &\vdots\\ \mathbf{a}^\top_{n} \mathbf{b}_1 & \mathbf{a}^\top_{n}\mathbf{b}_2& \cdots& \mathbf{a}^\top_{n} \mathbf{b}_m \end{bmatrix}. $$ [**Ne putem gândi la înmulțirea matrice-matrice $\mathbf{AB}$ ca efectuând $m$ produse matrice-vector sau $m \times n$ produse scalare și asamblând rezultatele împreună pentru a forma o matrice $n \times m$.**] În următorul fragment, efectuăm înmulțirea matriceală pe ''#A#'' și ''#B#''. Aici, ''#A#'' este o matrice cu două rânduri și trei coloane, iar ''#B#'' este o matrice cu trei rânduri și patru coloane. După înmulțire, obținem o matrice cu două rânduri și patru coloane. #tab mxnet B = np.ones(shape=(3, 4)) np.dot(A, B) #tab pytorch B = torch.ones(3, 4) torch.mm(A, B), A@B #tab tensorflow B = tf.ones((3, 4), tf.float32) tf.matmul(A, B) #tab jax B = jnp.ones((3, 4)) jnp.matmul(A, B) Termenul //înmulțire matrice-matrice// este adesea simplificat la //înmulțire matriceală// și nu trebuie confundat cu produsul Hadamard. ===== Norme ===== Unii dintre cei mai utili operatori în algebra liniară sunt //normele//. Informal, norma unui vector ne spune cât de //mare// este acesta. De exemplu, norma $\ell_2$ măsoară lungimea (Euclidiană) a unui vector. Aici, folosim o noțiune de //mărime// care privește magnitudinea componentelor unui vector (nu dimensionalitatea sa). O normă este o funcție $\| \cdot \|$ care mapează un vector la un scalar și satisface următoarele trei proprietăți: - Dat fiind orice vector $\mathbf{x}$, dacă scalăm (toate elementele) vectorului cu un scalar $\alpha \in \mathbb{R}$, norma sa se scalează corespunzător: $$\|\alpha \mathbf{x}\| = |\alpha| \|\mathbf{x}\|.$$ - Pentru orice vectori $\mathbf{x}$ și $\mathbf{y}$: normele satisfac inegalitatea triunghiului: $$\|\mathbf{x} + \mathbf{y}\| \leq \|\mathbf{x}\| + \|\mathbf{y}\|.$$ - Norma unui vector este nenegativă și dispare doar dacă vectorul este zero: $$\|\mathbf{x}\| > 0 \textrm{ pentru orice } \mathbf{x} \neq 0.$$ Multe funcții sunt norme valide și diferite norme codifică noțiuni diferite de mărime. Norma Euclidiană pe care am învățat-o cu toții în geometria școlii elementare atunci când calculam ipotenuza unui triunghi dreptunghic este rădăcina pătrată a sumei pătratelor elementelor unui vector. Formal, aceasta se numește [**norma $\ell_2$**] și este exprimată ca (**$$\|\mathbf{x}\|_2 = \sqrt{\sum_{i=1}^n x_i^2}.$$**) Metoda ''#norm#'' calculează norma $\ell_2$. #tab mxnet u = np.array([3, -4]) np.linalg.norm(u) #tab pytorch u = torch.tensor([3.0, -4.0]) torch.norm(u) #tab tensorflow u = tf.constant([3.0, -4.0]) tf.norm(u) #tab jax u = jnp.array([3.0, -4.0]) jnp.linalg.norm(u) [**Norma $\ell_1$**] este de asemenea comună și măsura asociată se numește distanța Manhattan. Prin definiție, norma $\ell_1$ însumează valorile absolute ale elementelor unui vector: (**$$\|\mathbf{x}\|_1 = \sum_{i=1}^n \left|x_i \right|.$$**) Comparativ cu norma $\ell_2$, este mai puțin sensibilă la valorile extreme (outliers). Pentru a calcula norma $\ell_1$, compunem valoarea absolută cu operația de sumă. #tab mxnet np.abs(u).sum() #tab pytorch torch.abs(u).sum() #tab tensorflow tf.reduce_sum(tf.abs(u)) #tab jax jnp.linalg.norm(u, ord=1) # same as jnp.abs(u).sum() Atât norma $\ell_2$ cât și $\ell_1$ sunt cazuri speciale ale //normelor// mai generale $\ell_p$: $$\|\mathbf{x}\|_p = \left(\sum_{i=1}^n \left|x_i \right|^p \right)^{1/p}.$$ În cazul matricelor, lucrurile sunt mai complicate. La urma urmei, matricele pot fi privite atât ca colecții de intrări individuale, //cât și// ca obiecte care operează asupra vectorilor și îi transformă în alți vectori. De exemplu, putem întreba cu cât mai lung ar putea fi produsul matrice-vector $\mathbf{X} \mathbf{v}$ relativ la $\mathbf{v}$. Această linie de gândire duce la ceea ce se numește norma //spectrală//. Pentru moment, introducem [**norma //Frobenius//, care este mult mai ușor de calculat**] și definită ca rădăcina pătrată a sumei pătratelor elementelor unei matrice: [**$$\|\mathbf{X}\|_\textrm{F} = \sqrt{\sum_{i=1}^m \sum_{j=1}^n x_{ij}^2}.$$**] Norma Frobenius se comportă ca și cum ar fi o normă $\ell_2$ a unui vector în formă de matrice. Invocarea următoarei funcții va calcula norma Frobenius a unei matrice. #tab mxnet np.linalg.norm(np.ones((4, 9))) #tab pytorch torch.norm(torch.ones((4, 9))) #tab tensorflow tf.norm(tf.ones((4, 9))) #tab jax jnp.linalg.norm(jnp.ones((4, 9))) Deși nu vrem să ne grăbim prea tare, putem deja planta o intuiție despre motivul pentru care aceste concepte sunt utile. În învățarea profundă, încercăm adesea să rezolvăm probleme de optimizare: //maximizăm// probabilitatea atribuită datelor observate; //maximizăm// venitul asociat cu un model de recomandare; //minimizăm// distanța dintre predicții și observațiile adevărului fundamental (ground truth); //minimizăm// distanța dintre reprezentările fotografiilor aceleiași persoane în timp ce //maximizăm// distanța dintre reprezentările fotografiilor unor persoane diferite. Aceste distanțe, care constituie obiectivele algoritmilor de învățare profundă, sunt adesea exprimate ca norme. ===== Discuții ===== În această secțiune, am trecut în revistă toată algebra liniară de care veți avea nevoie pentru a înțelege o parte semnificativă a învățării profunde moderne. Există mult mai multe despre algebra liniară, totuși, și o mare parte din ea este utilă pentru învățarea automată. De exemplu, matricele pot fi descompuse în factori, iar aceste descompuneri pot dezvălui structuri cu dimensiuni reduse în seturi de date din lumea reală. Există subdomenii întregi ale învățării automate care se concentrează pe utilizarea descompunerilor matriceale și generalizările lor la tensori de ordin înalt pentru a descoperi structura în seturi de date și a rezolva probleme de predicție. Dar această carte se concentrează pe învățarea profundă. Și credem că veți fi mai înclinați să învățați mai multă matematică odată ce v-ați murdărit mâinile aplicând învățarea automată la seturi de date reale. Așadar, în timp ce ne rezervăm dreptul de a introduce mai multă matematică mai târziu, încheiem această secțiune aici. Dacă sunteți dornici să învățați mai multă algebră liniară, există multe cărți excelente și resurse online. Pentru un curs intensiv mai avansat, luați în considerare consultarea Strang ((Strang.1993)), Kolter ((Kolter.2008)) și Petersen et al. ((Petersen.Pedersen.ea.2008)). Pentru a recapitula: * Scalarii, vectorii, matricele și tensorii sunt obiectele matematice de bază utilizate în algebra liniară și au zero, una, două și un număr arbitrar de axe, respectiv. * Tensorii pot fi secționați sau reduși de-a lungul axelor specificate prin indexare, sau operații precum ''#sum#'' și ''#mean#'', respectiv. * Produsele element cu element se numesc produse Hadamard. În contrast, produsele scalare, produsele matrice-vector și produsele matrice-matrice nu sunt operații element cu element și în general returnează obiecte având forme care sunt diferite de operanzi. * Comparativ cu produsele Hadamard, produsele matrice-matrice necesită mult mai mult timp pentru a fi calculate (timp cubic mai degrabă decât pătratic). * Normele capturează diverse noțiuni ale magnitudinii unui vector (sau matrice), și sunt aplicate în mod obișnuit diferenței a doi vectori pentru a măsura distanța dintre ei. * Normele vectoriale comune includ normele $\ell_1$ și $\ell_2$, iar normele matriciale comune includ normele //spectrale// și //Frobenius//. ===== Exerciții ===== - Demonstrați că transpusa transpusei unei matrice este matricea însăși: $(\mathbf{A}^\top)^\top = \mathbf{A}$. - Date fiind două matrice $\mathbf{A}$ și $\mathbf{B}$, arătați că suma și transpunerea comută: $\mathbf{A}^\top + \mathbf{B}^\top = (\mathbf{A} + \mathbf{B})^\top$. - Dată fiind orice matrice pătratică $\mathbf{A}$, este $\mathbf{A} + \mathbf{A}^\top$ întotdeauna simetrică? Puteți demonstra rezultatul folosind doar rezultatele celor două exerciții anterioare? - Am definit tensorul ''#X#'' de forma (2, 3, 4) în această secțiune. Care este ieșirea lui ''#len(X)#''? Scrieți răspunsul fără a implementa niciun cod, apoi verificați răspunsul folosind cod. - Pentru un tensor ''#X#'' de formă arbitrară, ''#len(X)#'' corespunde întotdeauna lungimii unei anumite axe a lui ''#X#''? Care este acea axă? - Rulați ''#A / A.sum(axis=1)#'' și vedeți ce se întâmplă. Puteți analiza rezultatele? - Când călătoriți între două puncte în centrul Manhattan-ului, care este distanța pe care trebuie să o acoperiți în termeni de coordonate, i.e., în termeni de bulevarde și străzi? Puteți călători pe diagonală? - Considerați un tensor de forma (2, 3, 4). Care sunt formele ieșirilor însumării de-a lungul axelor 0, 1 și 2? - Introduceți un tensor cu trei sau mai multe axe în funcția ''#linalg.norm#'' și observați ieșirea sa. Ce calculează această funcție pentru tensori de formă arbitrară? - Considerați trei matrice mari, să spunem $\mathbf{A} \in \mathbb{R}^{2^{10} \times 2^{16}}$, $\mathbf{B} \in \mathbb{R}^{2^{16} \times 2^{5}}$ și $\mathbf{C} \in \mathbb{R}^{2^{5} \times 2^{14}}$, inițializate cu variabile aleatoare Gaussiene. Vreți să calculați produsul $\mathbf{A} \mathbf{B} \mathbf{C}$. Există vreo diferență în amprenta de memorie și viteză, în funcție de faptul dacă calculați $(\mathbf{A} \mathbf{B}) \mathbf{C}$ sau $\mathbf{A} (\mathbf{B} \mathbf{C})$? De ce? - Considerați trei matrice mari, să spunem $\mathbf{A} \in \mathbb{R}^{2^{10} \times 2^{16}}$, $\mathbf{B} \in \mathbb{R}^{2^{16} \times 2^{5}}$ și $\mathbf{C} \in \mathbb{R}^{2^{5} \times 2^{16}}$. Există vreo diferență în viteză în funcție de faptul dacă calculați $\mathbf{A} \mathbf{B}$ sau $\mathbf{A} \mathbf{C}^\top$? De ce? Ce se schimbă dacă inițializați $\mathbf{C} = \mathbf{B}^\top$ fără a clona memoria? De ce? - Considerați trei matrice, să spunem $\mathbf{A}, \mathbf{B}, \mathbf{C} \in \mathbb{R}^{100 \times 200}$. Construiți un tensor cu trei axe prin stivuirea $[\mathbf{A}, \mathbf{B}, \mathbf{C}]$. Care este dimensionalitatea? Extrageți a doua coordonată a celei de-a treia axe pentru a recupera $\mathbf{B}$. Verificați că răspunsul este corect. [[https://discuss.d2l.ai/t/31|Discussions]]