# Основные понятия линейной алгебры
В даннном материале мы рассмотрим основные понятия линейной алгебры, которые необходимы для понимания основ машинного обучения. 
Основными понятиями линейной алгебры являются векторы и матрицы. Основные операции линейной алгебры определяются с помощью линейных отображений. Во многом именно линейные отображения делают линейную алгебру столь важной для прикладных задач. 

## Почему линейная алгебра столь важна?
Окружающий нас мир очень сложен и многие его проблемы имеют нелинейную природу. Изучение и понимание этих нелинейных связей в природе и в технике крайне сложно. Из-за этого инженеры, вместо того чтобы напрямую работать с нелинейными моделями, часто прибегают к линейным приблежениям этих моделей. Для этого нелинейную модель разбивают на многие меньшие части, где каждая часть в отдельности очень хорошо апроксимируется (т.е. приближается) линейным отображением. Как только проблема сформулирована в виде линейных отображений, сразу появляется возможность решить её очень эффективно и быстро с помощью методов линейной алгебры. 

В данном материале будут даны лишь общие понятия, описание терминов и некоторые теоремы без доказательств. Для более глубокого изучения нужно обратиться к специализированным курсам по линейной алгебре.

## Скалярная величина (scalar) 
Скалярная величина — величина, каждое значение которой может быть выражено одним действительным числом. Другими словами любое действительное число является скалярным значением. Термин появился в физике для обозначения величин, значения которых может быть выражено одним числом (длина, площадь, время, температура и т. д.), в отличии от других величин, которые выражаются с помощью векторов (например, сила имеет не только величину, но и направление). Скалярное значение обозначается маленькими буквами греческого (например, $\alpha$, $\beta$, $\gamma$) или латинского (например, a, b, c) алфавитов.

## Вектор (vector)
Вектором размера n мы будем называть одномерный массив из n чисел. Для обозначения вектора используется буква латинского алфавита со стрелочкой $\vec{x}$. Для записи вектора вместе с элементами в столбец используется следующая нотация
$$
\begin{align}
\vec{x} &= \begin{bmatrix}
        x_{1} \\
        x_{2} \\
        \vdots \\
        x_{n}
     \end{bmatrix}
\end{align}
$$
Вместо записи в столбец, веткор может быть записан в виде строки следующим образом:
1. $\vec{x} = (x_1, x_2, \ldots, x_n)$
2. $\vec{x} = \left<x_1, x_2, \ldots, x_n\right>$

Каждый элемент вектора является действительным числом. Для обозначения этого свойства используется нотация $x_i \in \mathbb{R}$, где $\mathbb{R}$ обозначает множество действительных чисел, а символ $\in$ принадлежность к данному множеству. Для обозначения того, что вектор размера n состоит из действительных чисел исопльзуется нотация $\vec{x} \in \mathbb{R^n}$.

В python мы будем использовать библиотеку NumPy для работы с векторами. Как было сказано выше, вектор размера n это одномерный массив с n числами. В NumPy мы можем создать $\vec{x} = (1, 2, 3)$ следующим образом


```python
import numpy as np

np.array([1, 2, 3])
```




    array([1, 2, 3])



### Стандартный единичный вектор (standard unit vector)
Стандартным или базисным (basis) единичным вектором называются векторы размера n, у которых один элемент равен 1, а все остальные 0. У стандартного вектора $\vec{e_j}$ элемент на позиции j равен 1, а все остальные позиции равны 0:
$$
\begin{align}
\vec{e_j} &= \begin{bmatrix}
        0 \\
        \vdots \\
        0 \\
        1 \\
        0 \\
        \vdots \\
        0
     \end{bmatrix}
\end{align}
$$
Единичные векторы в двухмерном пространстве обозначаются как $\hat{i}$ и $\hat{j}$
$$
\begin{align}
\hat{i}=\vec{e_0} &= \begin{bmatrix}
        1 \\
        0
     \end{bmatrix} &
\hat{j}=\vec{e_1} &= \begin{bmatrix}
        0 \\
        1
     \end{bmatrix}
\end{align}
$$
Единичные векторы в трехмерном пространстве обозначаются как $\hat{i}$, $\hat{j}$ и $\hat{k}$
$$
\begin{align}
\hat{i}=\vec{e_0} &= \begin{bmatrix}
        1 \\
        0 \\
        0
     \end{bmatrix} &
\hat{j}=\vec{e_1} &= \begin{bmatrix}
        0 \\
        1 \\
        0
     \end{bmatrix} &
\hat{k}=\vec{e_2} &= \begin{bmatrix}
        0 \\
        0 \\
        1
     \end{bmatrix}
\end{align}
$$
Стандартный единичный вектор имеет важное значение, которое мы увидем далее. Ниже приведен пример создания единичных векторов в трехмерном прастранстве с помощью NumPy


```python
i = np.array([1, 0, 0])
j = np.array([0, 1, 0])
k = np.array([0, 0, 1])
i, j, k
```




    (array([1, 0, 0]), array([0, 1, 0]), array([0, 0, 1]))



### Произведение вектора на скалярное значение
При произведении вектора на скалярное значение каждый элемент этого вектора умножается на скалярное значение. Например, если мы хотим скалярное значение $\alpha$ умножить на вектор $\vec{x}=(x_1, x_2, \ldots, x_n)$, то в результате получим 
$$
\begin{align}
\alpha\vec{x} &= \begin{bmatrix}
        \alpha x_1 \\
        \alpha x_2 \\
        \vdots \\
        \alpha x_n
     \end{bmatrix}
\end{align}
$$
Умножение вектора на скалярное значение также называется масштабированием вектора (vector scaling), так как при этом длина вектора увеличивается (или уменьшается если $0 < \alpha < 1$ ) на соответсвтвующее скалярное значение. 

Следующий код релизует умножение вектора $\vec{x}=(1, 2, 3)$ на скалярное значение 5


```python
x = np.array([1, 2, 3])
y = np.zeros_like(x)
a = 5
for i in range(len(x)):
    y[i] = x[i] * a
print(y)
```

    [ 5 10 15]


Однако, на практике писать такой код не стоит. Как было показано в разделе про векторизованные вычисления в NumPy, данный код можно реализовать значительно короче и эффективнее


```python
a * x
```




    array([ 5, 10, 15])



### Сложение и вычитание векторов (addition and substraction of vectors)
При сложении двух векторов каждый элемент первого вектора складывается с соответствующим по индексу элементом второго вектора. Т.е. если нам даны два вектора $\vec{x}=(x_1, x_2, \ldots, x_n)$ и $\vec{y}=(y_1, y_2, \ldots, y_n)$, то при их сложении мы получим следующий вектор
$$
\begin{align}
\vec{x} + \vec{y} &= \begin{bmatrix}
        x_1 + y_1 \\
        x_2 + y_2 \\
        \vdots \\
        x_n + y_n
     \end{bmatrix}
\end{align}
$$
Чтобы сложить два вектора их размер должен быть одинаковым. 

Следующий пример реализует сложение векторов на python


```python
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])
z = np.zeros_like(x)
for i in range(len(x)):
    z[i] = x[i] + y[i]
print(z)
```

    [5 7 9]


Реализация с помощью NumPy


```python
x + y
```




    array([5, 7, 9])



Вычитание веткоров выполняется аналогично сложению, только вместо складывания каждого элемента они вычитаются. Если даны два вектора $\vec{x}=(x_1, x_2, \ldots, x_n)$ и $\vec{y}=(y_1, y_2, \ldots, y_n)$, то при их вычитании мы получим следующий вектор
$$
\begin{align}
\vec{x} - \vec{y} &= \begin{bmatrix}
        x_1 - y_1 \\
        x_2 - y_2 \\
        \vdots \\
        x_n - y_n
     \end{bmatrix}
\end{align}
$$
Чтобы сложить два вектора их размер должен быть одинаковым. Следующий пример реализует сложение веткоров на python


```python
for i in range(len(x)):
    z[i] = x[i] - y[i]
print(z)
```

    [-3 -3 -3]


Реализация с помощью NumPy


```python
x - y
```




    array([-3, -3, -3])



### Линейная комбинация векторов (linear combination)
Допустим, что нам даны два вектора размера m: $\vec{x}, \vec{y} \in \mathbb{R^m}$ и два скалярных значения: $\alpha, \beta \in \mathbb{R}$. Линейной комбинацией векторов $\vec{x}, \vec{y}$ с коэффициентами $\alpha, \beta$ называется следующий вектор
$$
\begin{align}
\alpha \vec{x} + \beta \vec{y} &= \begin{bmatrix}
        \alpha x_1 + \beta y_1 \\
        \alpha x_2 + \beta y_2 \\
        \vdots \\
        \alpha x_n + \beta y_n
     \end{bmatrix}
\end{align}
$$
Линейная комбинация является одной из центральных понятий в линейной алгебре и многие другие понятия определяются с помощью нее. Линейная комбинация может быть обобщена на случай большего количества веткоров. Допустим у нас есть n векторов размера m: $\vec{x_1}, \vec{x_2}, \ldots, \vec{x_n} \in \mathbb{R^m}$ и n скалярных значений: $a_1, a_2, \ldots, a_n \in \mathbb{R}$. Тогда линейной комбинацией этих векторов называется вектор, полученный следующим образом
$$
\begin{align}
a_1 \vec{x_1} + a_2 \vec{x_2} + \cdots + a_n \vec{x_n} &= \begin{bmatrix}
         a_1 x_{1,1} + a_2 x_{1,2} + \cdots + a_n x_{1,n}\\
         a_1 x_{2,1} + a_2 x_{2,2} + \cdots + a_n x_{2,n}\\
         \vdots \\
         a_1 x_{m,1} + a_2 x_{m, 2} + \cdots + a_n x_{m, n}\\
     \end{bmatrix} &= \sum_{i=1}^{n} a_i \vec{x_i}
\end{align}
$$
Значение $x_{i,j}$ соответствует элементу i вектора j. Символ $\sum_{i=1}^{n}$ означает сумму элементов от 1 до n. Это математический символ соотвествующий циклу в языках программирования. Линейная комбинация двух векторов может быть реализована в python следующим образом


```python
x = np.array([1, 1, 1])
y = np.array([2, 2, 2])
a = 2
b = 1
z = np.zeros_like(x)
for i in range(len(x)):
    z[i] = a * x[i] + b * y[i]
print(z)
```

    [4 4 4]


Реализация с помощью NumPy


```python
a * x + b * y
```




    array([4, 4, 4])



### Представление векторов в виде линейной комбинации стандартных базисных векторов
Любой вектор можно представить с помощью линейной комбинации стандартных единичных векторов. Допустим у нас есть вектор $\vec{x}=(1, 2, 3)$. Этот вектор можно записать следующим обрзом:
$$
\begin{align}
\vec{x} &= \begin{bmatrix}
        1 \\
        2 \\
        3
     \end{bmatrix} &= 1 \begin{bmatrix}
        1 \\
        0 \\
        0
     \end{bmatrix} + 2 \begin{bmatrix}
        0 \\
        1 \\
        0
     \end{bmatrix} + 3 \begin{bmatrix}
        0 \\
        0 \\
        1
     \end{bmatrix} &= 1 \vec{e_1} + 2 \vec{e_2} + 3 \vec{e_3}
\end{align}
$$
В более общем виде, когда у нас есть вектор размера n $\vec{x} \in \mathbb{R}^n$, он может быть выражен с помощью стандартных единичных векторов следующим образом:
$$
\begin{align}
\vec{x} &= \begin{bmatrix}
        x_1 \\
        x_2 \\
        \vdots \\
        x_n
     \end{bmatrix} &= x_1 \begin{bmatrix}
        1 \\
        0 \\
        \vdots \\
        0
     \end{bmatrix} + x_2 \begin{bmatrix}
        0 \\
        1 \\
        \vdots \\
        0
     \end{bmatrix} + \cdots + x_n \begin{bmatrix}
        0 \\
        0 \\
        \vdots \\
        1
     \end{bmatrix} = x_1 \vec{e_1} + x_2 \vec{e_2} + \cdots + x_n \vec{e_n} = \sum_{i=1}^{n} x_i \vec{e_i}
\end{align}
$$
Такая форма представления векторов понадобится нам при определении матриц.


```python
x = np.array([1, 2, 3])
i = np.array([1, 0, 0])
j = np.array([0, 1, 0])
k = np.array([0, 0, 1])

y = x[0] * i + x[1] * j + x[2] * k
x == y
```




    array([ True,  True,  True], dtype=bool)



### Скалярное произведение векторов (dot product)
Допустим, что нам даны два вектора размера m: $\vec{x}, \vec{y} \in \mathbb{R^m}$. Скалярным или внутренним произведением этих двух векторов называется **скалярное** значение полученное следующим образом
$$
\begin{align}
\operatorname{dot(\vec{x}, \vec{y})} = \vec{x} \cdot \vec{y} = x_1 y_1 + x_2 y_2 + \cdots + x_n y_n = \sum_{i=1}^{n} x_i y_i
\end{align}
$$
Часто для обозначения скалярного произведения векторов используется нотация $\vec{x}^T\vec{y}$. Символ $T$ означает транспозицию. При применении данного оператора из вектора в столбец получается вектор в строку:
$$
\begin{align}
\vec{x}^T\vec{y} &= \begin{bmatrix}
        x_1 \\
        x_2 \\
        \vdots \\
        x_n
     \end{bmatrix}^T \begin{bmatrix}
        y_1 \\
        y_2 \\
        \vdots \\
        y_n
     \end{bmatrix} \\
     &= [x_1, x_2, \cdots, x_n] \begin{bmatrix}
        y_1 \\
        y_2 \\
        \vdots \\
        y_n
     \end{bmatrix} \\
     &= \sum_{i=1}^{n} x_i y_i
\end{align}
$$
Скалярное произведение векторов можно реализовать на python следующим образом


```python
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])
z = 0
for i in range(len(x)):
    z += x[i] * y[i]
print(z)
```

    32


Реализация с помощью NumPy


```python
x.dot(y)
```




    32



### Длина вектора (vector length)
Длина, величина или норма (length, magnitude, norm) вектора размера m: $\vec{x} \in \mathbb{R^m}$ обозначается как $||\vec{x}||$ и вычисляется следующим образом:
$$
\begin{align}
||\vec{x}|| = \sqrt{x_1^2 + x_2^2 + \cdots + x_n^2} = \sqrt{\sum_{i=1}^{n} x_i^2}
\end{align}
$$
Длина вектора, определенная таким образом, также называется евклидовой длиной. В двухмерном и трехмерном пространствах данное определение соответствует теореме пифагора для нахождения расстояния до точки от начала координат. Обратите внимание, что длина вектора также может быть определена через скалярное произведение вектора на самого себя:
$$
\begin{align}
||\vec{x}|| = \sqrt{\vec{x}^T\vec{x}}
\end{align}
$$
Вычисление длины вектора можно реализовать на python следующим образом


```python
x = np.array([3, 4])
l = 0
for i in range(len(x)):
    l += x[i] ** 2
l = l ** 0.5
print(l)
```

    5.0


Для вычисления длины вектора с помощью NumPy мы можем вомпользоваться функцией `norm` из модуля `numpy.linalg`


```python
np.linalg.norm(x)
```




    5.0



### Вектор-функция (vector function)
Вектор-функция - функция, значениями которой являются векторы двух, трёх или более измерений. Аргументами функции могут быть скалярные значения и векторы. Нас будут интересовать вектор-функции, которые на вход принимают вектор и на выходе выдают другой вектор. При этом размеры входного и выходного векторов могут быть разными. Вектор функция принимающая в качестве аргумента вектор размера m и отображающая его на вектор размера n обозначается следующим образом:
$$
\begin{align}
    f\colon \mathbb R^m \to \mathbb R^n
\end{align}
$$
Примерами векторных функций могут быть функция, которая выполняет сложение или вычитание векторов, функция, которая умножает скалярное значение на вектор и т.д. В разделе про векторизованные вычисления в NumPy мы видели примеры функций, которые принимают на вход массив и применяют функцию каждому элмементу этого массива. Данные функции также являются примерами вектор функций. Например, функция `np.exp` возводит число $e$ в степень каждого элемента вектора и возвращает вектор такого же размера


```python
x = np.array([1, 2, 3])
np.exp(x)
```




    array([  2.71828183,   7.3890561 ,  20.08553692])



### Линейное отображение (linear map)
Вектор функция $L\colon \mathbb R^m \to \mathbb R^n$ называется *линейным отображением* (или линейным оператором) если для всех $\vec{x}, \vec{y} \in \mathbb{R^m}$ и $\alpha \in \mathbb{R}$ удовлетворяет следующим двум условиям
1. Отображение произведения вектора на скалярное значение равно произведению скалярного значения на отображение вектора
$$L(\alpha \vec{x}) = \alpha L(\vec{x})$$
2. Отображение сложения векторов равно сложению отображений векторов
$$L(\vec{x} + \vec{y}) = L(\vec{x}) + L(\vec{y})$$

В качестве примера линейного отображения можно рассмотреть, функцию, которая вычисляет среднее значение всех элементов вектора. Например, если взять два вектора $\vec{x}=(1, 2, 3), \vec{y}=(4, 5, 6)$, то следующий код показывает свойства этого отображения


```python
x = np.array([1, 3, 5])
y = np.array([2, 4, 6])
a = 42

print('{0} = {1}'.format((a * x).mean(), a * x.mean()))
print('{0} = {1}'.format((x + y).mean(), x.mean() + y.mean()))
```

    126.0 = 126.0
    7.0 = 7.0


Из свойств линейного отображения следует равенство: $L(\alpha \vec{x} + \beta \vec{y}) = \alpha L(\vec{x}) + \beta L(\vec{y})$. Можно обобщить данное выражение на большее количество векторов. Допустим у нас есть n векторов размера m: $\vec{x_1}, \vec{x_2}, \ldots, \vec{x_n} \in \mathbb{R^m}$ и n скалярных значений: $a_1, a_2, \ldots, a_n \in \mathbb{R}$. Тогда верно следующее выражение, которое здесь приводится без доказательства:
$$L(a_1 \vec{x_1} + a_2 \vec{x_2} + \cdots + a_n \vec{x_n}) = a_1 L(\vec{x_1}) + a_2 L(\vec{x_2}) + \cdots + a_n L(\vec{x_n})$$

Можно записать данное выражение в более сжатом виде следующим образом:
$$L(\sum_{i=1}^n a_i \vec{x_i}) = \sum_{i=1}^n a_i L(\vec{x_i})$$

# Матрица
Выше мы видели, что любой вектор может быть записан в виде линейной комбинации базисных векторов: 
$$\vec{x} = \sum_{i=1}^{m} x_i \vec{e_i}$$
Поэтому линейное отображение вектора эквивалентно выражению: 
$$L(\vec{x}) = L(\sum_{i=1}^{m} x_i \vec{e_i})$$
Однако мы только что видели, что линейное отображение суммы векторов равно сумме линейных отображений каждого из этих векторов. Поэтому линейное отображение вектора можно записать следующим образом: 
$$L(\vec{x}) = L(\sum_{i=1}^{m} x_i \vec{e_i}) = \sum_{i=1}^{m} x_i L(\vec{e_i})$$
Если определить вектор $\vec{u_i}=L(\vec{e_i})$, то любое линейное отображение может быть полностью определено через линейную комбинацию векторов $\vec{u_i}$: 
$$L(\vec{x}) = \sum_{i=1}^m x_i \vec{u_i}$$
Эти векторы ${\vec{u_1}, \vec{u_2}, \ldots, \vec{u_n}}$ можно записать в двухмерном виде следующим образом:
$$
\begin{align}
A &= (\vec{u_1}, \vec{u_2}, \ldots, \vec{u_n}) &= \begin{pmatrix}
        u_{1,1} & u_{1,2} & \cdots & u_{1,n} \\
        u_{2,1} & u_{2,2} & \cdots & u_{2,n} \\
        \vdots & \vdots & \ddots & \vdots \\
        u_{m,1} & u_{m,2} & \cdots & u_{m,n} \\
     \end{pmatrix}
\end{align}
$$
Данная двухмерная структура называется матрицей линейного отображения. Любое линейное отображение может быть применено для каждого базисного вектора и результат может быть записан в виде матрицы. Линейное отображение может быть записано с помощью матрицы следующим образом:
$$L(\vec{x}) = A\vec{x}$$
## Умножение матрицы на вектор
Выражение $A\vec{x}$ называется умножением матрицы на вектор. Так как это выражение эквивалентно линейному отображению, то умножение матрицы на вектор можно получить из определения линейного отображения:
$$
\begin{align}
A\vec{x} &= L(\vec{x}) &= x_1\vec{u_1} + x_2\vec{u_2} + \cdots + x_n\vec{u_n} &= x_1 \begin{bmatrix}
        u_{1,1} \\
        u_{2,1} \\
        \vdots \\
        u_{m,1}
     \end{bmatrix} + x_2 \begin{bmatrix}
        u_{1,2} \\
        u_{2,2} \\
        \vdots \\
        u_{m,2}
     \end{bmatrix} + \cdots + x_n \begin{bmatrix}
        u_{1,n} \\
        u_{2,n} \\
        \vdots \\
        u_{m,n}
     \end{bmatrix} &= \begin{pmatrix}
        x_1 u_{1,1} + x_2 u_{1,2} + \cdots + x_n u_{1,n} \\
        x_1 u_{2,1} + x_2 u_{2,2} + \cdots + x_n u_{2,n} \\
        \vdots \\
        x_1 u_{m,1} + x_2 u_{m,2} + \cdots + x_n u_{m,n} \\
     \end{pmatrix}
\end{align}
$$
Умножение матрицы на вектор можно реализовать на python следующим образом


```python
A = np.array([[0, -1], 
              [1, 0]])
x = np.array([1, 0])
y = np.zeros_like(x)
for i in range(len(x)):
    for j in range(len(x)):
        y[i] += A[i, j] * x[j]
y
```




    array([0, 1])



В NumPy для умножения матрицы на вектор можно использовать функцию `dot`


```python
A.dot(x)
```




    array([0, 1])



## Умножение матриц
Перед тем как определить умножение двух матриц рассмотрим последовательное применение двух линейных отображений. Пусть у нас есть два линейных отображения $L_A\colon \mathbb R^k \to \mathbb R^m$ и $L_B\colon \mathbb R^n \to \mathbb R^k$. Тогда последовательное применение этих двух линейных отображений даст нам третье линейное отображение $L_C\colon \mathbb R^n \to \mathbb R^m$:
$$L_C(\vec{x}) = L_A(L_B(\vec{x}))$$
Выше мы видели, что любое линейное отображение может быть представлено в виде соответствующей матрицы. Допустим, матрица $A \in \mathbb{R}^{m \times k}$ представляет $L_A$ и матрица $B \in \mathbb{R}^{k \times n}$ представляет $L_B$. Тогда матрица $C \in \mathbb{R}^{m \times n}$, которая представляет $L_C$, вычисляется с помощью умножения двух матриц: $C=AB$. Линейное отображение $L_C$ может быть записано с помощью этих матриц следующим образом:
$$L_C(\vec{x})=C\vec{x}=(AB)\vec{x}=(A(B\vec{x}))$$
Каждый элемент матрицы $C$ может быть вычислен по следующей формуле:
$$
c_{i,j} = \sum_{k=1}^{n} a_{i,k} b_{k,j}
$$
Рассмотрим следующий пример:
$$
\begin{align}
C &= \begin{pmatrix}
        0 & -1 \\
        1 & 0
     \end{pmatrix} \times \begin{pmatrix}
        -1 & 0 \\
        0 & -1
     \end{pmatrix} &= \begin{pmatrix}
        0 * (-1) + (-1) * 0 & 0 * 0 + (-1) * (-1) \\
        1 * (-1) + 0 * 0 & -1 * 0 + 0 * (-1)
     \end{pmatrix} &= \begin{pmatrix}
        0 & 1 \\
        -1 & 0
     \end{pmatrix}
\end{align}
$$
Умножение матриц можно реализовать на python следующим образом


```python
A = np.array([[0, -1], 
              [1, 0]])
B = np.array([[-1, 0], 
              [0, -1]])
C = np.zeros_like(A)
for i in range(A.shape[0]):
    for j in range(B.shape[1]):
        for k in range(A.shape[1]):
            C[i, j] += A[i, k] * B[k, j]
C
```




    array([[ 0,  1],
           [-1,  0]])



В NumPy для умножения матриц можно использовать функцию `dot`


```python
A.dot(B)
```




    array([[ 0,  1],
           [-1,  0]])



5b8c7bcdfba3a543b7f8703d95023a4576a19a0e
ipynb
Jupyter Notebook
04.01 Linear Algebra - definitions and main concepts.ipynb
rustam-fork/ml-course-uz
e1554d4c69bf0e421aa596d77aab65639df1ff73
MIT
2018-01-05T09:24:49.000Z
2021-04-24T03:25:25.000Z
04.01 Linear Algebra - definitions and main concepts.ipynb
rustam-fork/ml-course-uz
e1554d4c69bf0e421aa596d77aab65639df1ff73
MIT
2019-11-11T18:34:53.000Z
2019-11-13T15:56:10.000Z
04.01 Linear Algebra - definitions and main concepts.ipynb
rustam-fork/ml-course-uz
e1554d4c69bf0e421aa596d77aab65639df1ff73
MIT
2018-01-05T10:26:47.000Z
2022-01-25T07:48:33.000Z
Qwen/Qwen-72B
1. YES
2. YES