«Алгебра» примерно переводится как «отношения», а линейный означает «линейный», таким образом, «линейная алгебра» означает «линейные отношения».

Алгебра

Так почему же школьная алгебра состоит только из букв, а не из чисел?

В моей голове алгебра - это просто расположение букв в соответствии с определенными правилами. Однако вот способ взглянуть на это.

Рассмотрим уравнение ниже. Вы не знаете, какое значение имеют x и y.

(x + y)² = x² + 2xy + y²

Однако мы все еще можем проработать отношения между (x + y)² и x² + 2xy + y². Мы знаем, что оба выражения равны, и мы знаем соотношение между значением x и y и каждой стороной уравнения. Мы могли бы написать это как функцию:

f(x, y) = (x + y)²

Линейный

Линейные отношения - это отношения, похожие на то, что вы можете описать в строке. Линейные отношения предсказуемы. Вы удваиваете ввод и получаете удвоение вывода.

Это не линейная зависимость

f(x) = x²

Если я удвою вход, выход увеличится в четыре раза. Это пример линейной зависимости:

f(x) = 4x

Однако это не линейная зависимость, даже если вы так думаете.

f(x) = 2x + 4

Потому что f(1) равно 6, а f(2) равно 8. Мы удвоили ввод, но не получили удвоения вывода.

Мы можем сделать его линейным, добавив еще один параметр.

f(x, y) = 2x + 4y

Если мы удвоим входы, то теперь мы удвоим и выход.

f(2x, 2y) = 2*2x + 2*4y
2(2x + 4y) = 2f(x, y)

Таким образом, мы можем создавать линейные функции с некоторой гибкостью, просто добавляя несколько аргументов.

Связь матрицы и вектора

Проблема с этими функциями в том, что когда у них много аргументов, они становятся повторяющимися, и им нужно много писать.

Вот почему используются матрицы и векторы. Мы можем выразить предыдущую функцию f как скалярное произведение.

[2, 4] ⋅ [x, y] = 2x + 4y
f = [2, 4]
f ⋅ [x, y] = 2x + 4y

Таким образом, вы можете думать о векторе f как об операции, выполняемой над входным вектором [x, y].

Но при чем тут матрицы? Проблема с надежной старой нотацией функций заключается в том, что вы получаете корч, описывающий все это, если вы работаете с большим количеством данных.

Матрицы - это, по сути, приложение для работы с электронными таблицами 1700-х годов. Им нужен был компактный способ работы с большим количеством чисел. Допустим, вы хотите применить функцию f к множеству чисел. Мы могли бы написать:

f(3, 4) = 2*3 + 4*4 = 22
f(2, 6) = 2*3 + 4*4 = 28
f(12, 1) = 2*12 + 4*1 = 28

Вместо этого мы могли бы рассматривать f как вектор-строку, а входные данные - как матрицу. Каждый столбец в матрице представляет собой набор входных параметров.

В этом случае мы будем использовать синтаксис языка Julia, где [x y z] представляет вектор-строку, а [x, y, z] представляет вектор-столбец. Используя синтаксис Julia, я могу представить предыдущие вычисления в более компактной форме:

f = [2 4]
X = [3 2 12; 4 6 1]
f * X = [2*3 + 4*4 2*3 + 4*4 2*12 + 4*1]
f * X = [22 28 28]

Преимущества матрицы

Это также дает подсказку, почему функция или операция, которую мы выполняем над входом, должны быть не вектором, а матрицей. Что произойдет, если вы захотите выполнить несколько операций с одним и тем же входом?

Допустим, мы хотим рассчитать общую стоимость покупки некоторых товаров x, y и z. Скажем, x стоит 150 долларов, y стоит 50, а z стоит 300. Мы могли бы выразить это как:

costx(x) = 150x
costy(y) = 50y
costz(z) = 300z

А еще нужно платить налоги по ставкам 20%, 10% и 30% соответственно. Мы можем выразить это как:

taxx(x) = 1.2x
taxy(y) = 1.1y
taxz(z) = 1.3z

А затем, чтобы все это сложить, мы используем sum, определенный как:

sum(x, y, z) = taxx(costx(x)) + taxy(costy(y)) + taxz(costz(z))

Как видите, это быстро становится громоздким и громоздким. В частности, если мы имеем дело не с 3 предметами, а со 100 различными предметами. А теперь представьте, что нам нужно сделать этот расчет для 50 различных поставок тех же 100 разных продуктов.

Есть способ обойти это. Чтобы понять суть идеи, давайте поиграемся с некоторыми альтернативными способами определения наших функций:

cost(x, y, z) = 150x + 50y + 300z
tax(x, y, z) = 1.2x + 1.1y + 1.3z

Теперь, если вам нужна только стоимость x товаров, вы просто напишите cost(x, 0, 0). Если вам нужна стоимость y предметов, то это cost(0, y, 0). Мы можем использовать это в нашей функции sum.

sum(x, y, z) = tax(cost(x, 0, 0), 0, 0) + 
              tax(0, cost(0, y, 0), 0) +
              tax(0, 0, cost(0, 0, z))

Мы можем выразить это более элегантно, используя матрицу. Идея здесь в том, что каждая строка в матрице - это операция, которую вы выполняете над входом, которая дает один результат. Затем я могу вместо этого определить cost как матрицу:

cost = [150 0 0; 0 50 0; 0 0 300]
cost * [x, y, z] = [150x, 50x, 300z]

Вы можете видеть, что cost можно использовать для получения 3 разных результатов, по одному для x, y и z. Давайте определим tax таким же образом и посмотрим, как мы можем сложить все, используя матрицы.

tax = [1.2 0 0; 0 1.1 0; 0 0 1.3]
tax * cost * [x, y, z] = [1.2 * 150 * x, 1.1*50*y, 1.3*300*z]
tax * cost * [x, y, z] = [180x, 55y, 390z]

Давайте посмотрим, как это будет выглядеть на языке программирования Julia, используя встроенную среду REPL (read-Assessment-print-loop).

julia> cost = [150 0 0; 0 50 0; 0 0 300]
3×3 Array{Int64,2}:
 150   0    0
   0  50    0
   0   0  300

julia> tax = [1.2 0 0; 0 1.1 0; 0 0 1.3]
3×3 Array{Float64,2}:
 1.2  0.0  0.0
 0.0  1.1  0.0
 0.0  0.0  1.3

Мы заказываем товары двумя разными партиями. Сначала мы заказываем по одному экземпляру каждого, а во второй партии мы заказываем 3, 2 и 1 товар соответственно.

julia> amounts = [1 3;1 2; 1 1]
3×2 Array{Int64,2}:
 1  3
 1  2
 1  1

Это дает нам следующие цены на каждый товар для каждой партии

julia> tax * cost * amounts
3×2 Array{Float64,2}:
 180.0  540.0
  55.0  110.0
 390.0  390.0

Следующее, что нам понадобится, это матрица, которая суммирует результаты. В этом случае мы получаем только один результат для каждой партии. Это означает, что в этой матрице только одна строка. Каждая строка в матрице должна рассматриваться как операция, которую вы хотите выполнить над входом.

julia> sum = [1 1 1]
1×3 Array{Int64,2}:
 1  1  1

julia> sum * tax * cost * amounts
1×2 Array{Float64,2}:
 625.0  1040.0

Мы можем проверить правильность этого результата:

julia> 180 + 55 + 390
625

Прелесть этого подхода заключается в том, что мы можем комбинировать несколько функций расчета затрат, налогов и т. Д. Посредством умножения матриц. Это позволяет нам компактно представить операции, которые мы выполняем. Функции, определенные для скалярных операций, становились неудобными в использовании по мере роста количества элементов и операций.

Примеры полезных матричных операций

Чтобы привыкнуть к представлению о матрице как о наборе операций, выполняемых над некоторыми входными данными, которые также могут быть матрицей, нужно время, чтобы привыкнуть. Но вот несколько примеров, чтобы довести дело до конца.

julia> inputs = [3, 2, 1]
3-element Array{Int64,1}:
 3
 2
 1

Будет выбрано только значение y:

julia> picky = [0 1 0]
1×3 Array{Int64,2}:
 0  1  0

julia> picky * inputs
1-element Array{Int64,1}:
 2

Обычно для каждой строки вы ставите 1 для значения, которое хотите выбрать, и 0 для тех, которые вам не нужны. Таким образом, мы можем определить матрицу, которая изменяет порядок элементов, сначала выбирая z, затем y и, наконец, x

julia> swapper = [0 0 1; 0 1 0; 1 0 0]
3×3 Array{Int64,2}:
 0  0  1
 0  1  0
 1  0  0

julia> swapper * inputs
3-element Array{Int64,1}:
 1
 2
 3

Или как насчет удвоения каждого элемента ввода:

julia> doubler = [2 0 0; 0 2 0; 0 0 2]
3×3 Array{Int64,2}:
 2  0  0
 0  2  0
 0  0  2

julia> doubler * inputs
3-element Array{Int64,1}:
 6
 4
 2

Представление дополнения

Пока что мы представили только функции формы:

f(x) = ax

Но нам нужна гибкость представления функций в форме:

f(x) = ax + b

Где b - некоторая константа. Оказывается, сделать это довольно просто. Вы просто требуете, чтобы у всех входных данных был последний элемент, который всегда имеет значение 1. Затем мы можем представить f следующим образом:

f = [a b]
f * [x, 1] = ax + b

Это работает для любого количества аргументов.

f = [a b c d]
f * [x, y, z, 1] = ax + by + cz + d

Аффинные преобразования

На этом фоне, возможно, легче понять, как работают аффинные преобразования, используемые в компьютерной графике для перевода, поворота и масштабирования объектов.

Если вы переводите точку на (dx, dy) в 2D, вы должны использовать следующую матрицу:

1  0  dx
 0  1  dy
 0  0  1

Почему это работает? Вы воспринимаете каждую строку как операцию. Первая операция создает новую координату x, а вторая операция создает координату y.

Мы можем рассматривать его как две функции f и g, которые принимают координаты x и y в качестве входных данных и создают новые координаты x' и y'.

x' = f(x, y, 1)
y' = g(x, y, 1)

Итак, что нужно сделать f, так это выбрать координату x, потому что она должна давать первую координату. Сбор x выполняется с помощью 1 0 0. g, представляющий выбор второй строки y с 0 1 0.

Помните фокус с константами. Имея z = 1 всегда, мы можем использовать третий аргумент для создания смещений. Итак, мы определяем f как 1 0 dx, чтобы выбрать x и добавить к нему смещение dx.

Но зачем нам третий ряд?

Обратите внимание, что нам нужны 3 столбца, чтобы иметь возможность предлагать смещение, такое как dx и dy. Для этого на входе должно быть 3 строки, где последняя строка равна 1.

Помните, что при матричном умножении столбцы в первой матрице должны соответствовать строкам во втором аргументе. Если для вас это не очевидно, прочтите мой рассказ о том, почему умножение матриц работает именно так.

Таким образом, если бы наша операционная матрица не имела третьей строки, мы бы столкнулись с проблемой, заключающейся в том, что выводился бы двухстрочный вектор-столбец. Помните, что каждая строка аффинной матрицы порождает компонент. Имея всего две строки, вы получите только двухкомпонентный вывод.

Это означает, что ваш результат нельзя умножить на другую аффинную матрицу. Вы также не можете перемножить две аффинные матрицы друг на друга. Вы можете умножить матрицу 3x3 на матрицу 3x3, потому что строки и столбцы соответствуют друг другу.

Однако вы не можете умножить матрицу 2x3 на матрицу 2x3. Таким образом, не было бы возможности объединить несколько матриц в одну матрицу. Допустим, вы хотите выполнить масштабирование, поворот и перевод. Вы можете объединить все эти три матрицы в матрицу. Вы можете повторно использовать эту матрицу, умножив ее на миллион точек, если хотите.