Умножение матриц: смысл и реализация вручную
Умножение матриц — это склейка двух преобразований в одно: сначала применить B, потом A.
Произведение матриц A·B: элемент на позиции (i, j) — скалярное произведение i-й строки A на j-й столбец B. Размеры должны согласоваться: (m×n)·(n×p) = (m×p).
Смысл: композиция преобразований
Если матрица — это преобразование, то произведение матриц — это два преобразования подряд. Применить к вектору сначала B, потом A — то же самое, что применить одну матрицу A·B. Поэтому в нейросети несколько линейных слоёв подряд — это перемножение их матриц. Порядок важен: A·B и B·A в общем случае разные (сначала повернуть, потом растянуть — не то же, что наоборот).
Правило «строка на столбец»
Чтобы получить элемент результата в строке i и столбце j, берут i-ю строку левой матрицы и j-й столбец правой, перемножают поэлементно и складывают — обычное скалярное произведение. Размеры обязаны согласоваться: число столбцов левой = числу строк правой.
def matmul(A, B):
n = len(A) # строк в A
m = len(B) # строк в B = столбцов в A
p = len(B[0]) # столбцов в B
result = []
for i in range(n):
row = []
for j in range(p):
s = sum(A[i][k] * B[k][j] for k in range(m))
row.append(s)
result.append(row)
return result
A = [[1, 2],
[3, 4]]
B = [[5, 6],
[7, 8]]
print("A * B =", matmul(A, B))
print("B * A =", matmul(B, A)) # другой результат!
Вывод:
A * B = [[19, 22], [43, 50]] B * A = [[23, 34], [31, 46]]
Проверим композицию на векторе
Убедимся, что «применить B, потом A» равно «применить A·B». Сначала растянем вектор матрицей S, потом повернём матрицей R; затем сделаем то же одной матрицей R·S.
def matmul(A, B):
m = len(B)
return [[sum(A[i][k] * B[k][j] for k in range(m)) for j in range(len(B[0]))]
for i in range(len(A))]
def matvec(M, v):
return [sum(M[i][j] * v[j] for j in range(len(v))) for i in range(len(M))]
S = [[2, 0], [0, 2]] # растяжение в 2 раза
R = [[0, -1], [1, 0]] # поворот на 90
v = [1, 0]
# Путь 1: применяем по очереди
step1 = matvec(S, v)
step2 = matvec(R, step1)
# Путь 2: одна склеенная матрица R*S
RS = matmul(R, S)
once = matvec(RS, v)
print("По очереди:", step2)
print("Через R*S :", once)
Вывод:
По очереди: [0, 2] Через R*S : [0, 2]
Согласованность размеров — частая ошибка
| A | B | A·B |
| 2×3 | 3×4 | 2×4 (ок, 3 = 3) |
| 2×3 | 2×4 | ошибка (3 ≠ 2) |
| m×n | n×p | m×p |
В реальном коде на numpy это пишется одним символом @, но размеры всё равно надо держать в голове — несогласованные формы дают самую частую ошибку при работе с нейросетями.
# В numpy то же умножение — оператор @ (в браузере не запустится)
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(A @ B) # [[19 22] [43 50]]
Итог
- Умножение матриц = композиция преобразований (сначала B, потом A).
- Элемент (i, j) — скалярное произведение i-й строки A на j-й столбец B.
- Размеры: (m×n)·(n×p) = (m×p); внутренние числа обязаны совпасть.
- Порядок важен: A·B ≠ B·A в общем случае.