Тензоры. Векторно-матричные операции

Смотреть материал на YouTube | RuTube

Пришло время познакомиться с одной из фундаментальных возможностей пакета PyTorch – векторно-матричными вычислениями. На одном из прошлых занятий мы с вами уже видели, как можно поэлементно умножать один тензор на другой:

a = torch.arange(1, 10).view(3, 3)
b = torch.arange(10, 19).view(3, 3)
r1 = a * b
r2 = torch.mul(a, b)

Получим результат:

tensor([[ 10,  22,  36],
        [ 52,  70,  90],
        [112, 136, 162]])

Матричное умножение

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

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

c = torch.matmul(a, b) # перемножение с эффектом транслирования

либо:

c = torch.mm(a, b) # перемножение без эффекта транслирования

Обе эти функции возвращают двумерный тензор с результатом матричного умножения:

tensor([[ 84,  90,  96],
        [201, 216, 231],
        [318, 342, 366]])

Однако функция torch.mm выполняет перемножение без возможности транслирования, а функция torch.matmul позволяет транслировать тензор при необходимости согласования размерностей. Давайте я поясню это на конкретном примере. Объявим дополнительно еще один вектор:

v = torch.LongTensor([-1, -2, -3])

Здесь используется тип long, чтобы типы данных перемножаемых тензоров a и v совпадали. Тогда будет вполне допустимы команды:

c = torch.matmul(a, v) # матрица на вектор
c = torch.matmul(v, a) # вектор на матрицу

Но недопустима команда:

c = torch.mm(a, v) # ошибка

так как здесь необходимы матрицы (тензоры) согласованных размеров. Какую именно функцию использовать на практике, зависит от конкретной решаемой задачи.

Или же вместо этих функций можно использовать аналогичные методы:

c = a.mm(b)
c = a.matmul(v)

В PyTorch дополнительно к обычному матричному умножению добавляется еще одна команда:

torch.bmm

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

bx = torch.randn(7, 3, 5)
by = torch.randn(7, 5, 4)
bc = torch.bmm(bx, by)
bc.size() # тензор 7x3x4

Как видите, первая ось определяет количество матриц в батче (пакете), которые нужно перемножить между собой. То есть, перемножаются независимо семь матриц между собой и на выходе формируется тоже семь матриц, но с размерностями 3x4.

Векторное умножение

Аналогичные операции можно выполнять и с векторами. Математически, если у нас имеются два вектора:

то их умножение можно реализовать двумя способами:

 

и

 

Первое умножение называется скалярным и реализуется либо через функцию torch.dot (или аналогичный метод):

a = torch.arange(1, 10, dtype=torch.float32)
b = torch.ones(9)
c = torch.dot(a, b) # tensor(45.)

Либо с помощью оператора @:

c = a @ b

Другой способ умножения (внешнее умножение векторов) реализуется с помощью функции (или аналогичного метода):

c = torch.outer(a, b)

получим результат в виде следующей матрицы:

tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [2., 2., 2., 2., 2., 2., 2., 2., 2.],
        [3., 3., 3., 3., 3., 3., 3., 3., 3.],
        [4., 4., 4., 4., 4., 4., 4., 4., 4.],
        [5., 5., 5., 5., 5., 5., 5., 5., 5.],
        [6., 6., 6., 6., 6., 6., 6., 6., 6.],
        [7., 7., 7., 7., 7., 7., 7., 7., 7.],
        [8., 8., 8., 8., 8., 8., 8., 8., 8.],
        [9., 9., 9., 9., 9., 9., 9., 9., 9.]])

Умножение вектора на матрицу

Наконец, рассмотрим умножение вектора на матрицу. Это также можно записать двумя способами:

 

или

 

Для реализации второго способа, зададим одномерный и двумерный тензоры:

a = torch.FloatTensor([1,2,3])
b = torch.arange(4, 10, dtype=torch.float32).view(2, 3) # матрица 2x3

И, затем, воспользуемся функцией:

r = torch.mv(b, a) # tensor([32., 50.])

или методом:

r = b.mv(a) # tensor([32., 50.])

Причем, обратите внимание, в функции (или методе) mv тензор b должен представлять собой матрицу, а тензор a – вектор. Если их поменять местами:

r = torch.mv(a, b) # ошибка

то получим ошибку.

Элементы линейной алгебры

Из высшей математики хорошо известно, что матрицы можно использовать для решения систем линейных уравнений. Для этого в PyTorch существует модуль linalg. Давайте рассмотрим некоторые из его функций.

Предположим, имеется квадратная матрица 3x3:

a = torch.FloatTensor([(1, 2, 3), (1, 4, 9), (1, 8, 27)])

Первым делом вычислим ранг этого тензора, чтобы быть уверенным, что он состоит из линейно независимых строк и столбцов:

 torch.linalg.matrix_rank(a) # ранг равен 3

Если ранг матрицы совпадает с ее размерностью, значит, она способна описывать систему из трех независимых линейных уравнений. В нашем случае, система уравнений будет иметь вид:

Здесь   - некие числа линейного уравнения. Например, возьмем их равными:

y = torch.FloatTensor([10, 20, 30])

Тогда корни уравнения можно вычислить с помощью функции solve:

torch.linalg.solve(a, y) # tensor([-5.0000, 10.0000, -1.6667])

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

 

Откуда получаем решения :

 

На уровне пакета PyTorch это делается так:

invA = torch.linalg.inv(a) # вычисление обратной матрицы
x = torch.mv(invA, y) # вычисление корней

Получим результат:

tensor([-5.0000, 10.0000, -1.6667])

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

Функция

Описание

linalg.cholesky()

Разложение Холецкого

linalg.qr()

QR-разложение матрицы

linalg.svd()

Сингулярное (SVD) разложение матрицы

linalg.norm()

Норма матрицы или вектора

linalg.cond()

Число обусловленности матрицы

linalg.det()

Определитель (детерминант) матрицы

linalg.matrix_rank()

Вычисление ранга матрицы по алгоритму SVD

torch.trace()

Сумма диагональных элементов массива

linalg.eig()

Вычисление собственных значений и правых собственных векторов

linalg.eigvals()

Вычисление собственных значений матрицы

linalg.solve()

Решение линейного матричного уравнения

linalg.tensorsolve()

Решение линейного тензорного уравнения

linalg.lstsq()

Решает задачу поиска наименьших квадратов для линейного матричного уравнения

linalg.inv()

Вычисление обратной матрицы

linalg.pinv()

Вычисление псевдообратной (Мура-Пенроуза) матрицы

linalg.tensorinv()

Вычисление обратного тензора (N-мерного массива)

Конечно, это не все математические функции пакета PyTorch. Полное описание смотрите на сайте с официальной документацией:

https://pytorch.org/docs/stable/index.html

Видео по теме