3 способи множення матриць у Python

Множення матриць у Python: Повний посібник

У цій статті ми детально розглянемо процес множення матриць в Python. Ви ознайомитеся з необхідними умовами для коректного множення матриць, навчитеся створювати власні функції для цієї операції, а також дізнаєтеся про альтернативні підходи, використовуючи вкладені списки та потужні інструменти бібліотеки NumPy.

Спочатку ми розглянемо основи множення матриць, а потім перейдемо до практичних прикладів реалізації в Python. Ви дізнаєтесь про різні методи, включаючи створення власної функції, використання вкладених списків і ефективне застосування бібліотеки NumPy.

Перевірка коректності множення матриць

Перед тим, як перейти до кодування, важливо згадати основні принципи множення матриць. Множення матриць A і B можливе лише тоді, коли кількість стовпців матриці A збігається з кількістю рядків матриці B.

Ця умова не є випадковою; вона випливає безпосередньо з алгоритму множення матриць. Давайте розберемося, чому саме так.

У загальному вигляді, матриця A має m рядків і n стовпців, а матриця B має n рядків і p стовпців. Саме така структура дозволяє проводити множення.

Розмір результуючої матриці

Елемент результуючої матриці C з індексами (i, j) є скалярним добутком i-го рядка матриці A і j-го стовпця матриці B.

Отже, для обчислення кожного елемента матриці C, потрібно виконати скалярний добуток відповідних рядків і стовпців матриць A і B.

Виконавши ці обчислення для всіх елементів, ми отримаємо матрицю C розміром m x p, що означає m рядків і p стовпців, як продемонстровано на зображенні нижче.

Скалярний добуток двох векторів a і b обчислюється за такою формулою:

З цього випливають такі висновки:

  • Скалярний добуток має сенс лише для векторів однакової довжини.
  • Тому, для коректного множення матриць, кожен рядок у A і кожен стовпець у B повинні мати однакову кількість елементів.
  • У нашому прикладі кожен рядок A і кожен стовпець B містять n елементів.

Отже, кількість стовпців матриці A (n) повинна дорівнювати кількості рядків матриці B (n). Це і є умова коректності множення матриць.

Сподіваємося, що тепер умова множення матриць та обчислення елементів матриці-добутку є зрозумілими. Тепер перейдемо до кодування.

Створення власної функції Python для множення матриць

Почнемо зі створення спеціалізованої функції для множення матриць.

Ця функція повинна виконувати наступні кроки:

  • Приймати дві матриці, A та B, як аргументи.
  • Перевіряти, чи можливе множення між матрицями A та B.
  • У разі можливості, обчислювати добуток матриць та повертати результуючу матрицю C.
  • У разі неможливості, повертати повідомлення про помилку.

Крок 1: Створіть матриці за допомогою NumPy. Ви можете використовувати функцію `random.randint()` або вкладені списки Python.

import numpy as np
np.random.seed(27)
A = np.random.randint(1,10,size = (3,3))
B = np.random.randint(1,10,size = (3,2))
print(f"Матриця A:\n {A}\n")
print(f"Матриця B:\n {B}\n")
  

Виведе:

Матриця A:
 [[4 9 9]
 [9 1 6]
 [9 2 3]]

Матриця B:
 [[2 2]
 [5 7]
 [4 4]]
  

Крок 2: Розробимо функцію `multiply_matrix(A,B)`, яка приймає дві матриці та повертає матрицю-добуток C.

def multiply_matrix(A,B):
  global C
  if  A.shape[1] == B.shape[0]:
    C = np.zeros((A.shape[0],B.shape[1]),dtype = int)
    for row in range(A.shape[0]): 
        for col in range(B.shape[1]):
            for elt in range(len(B)):
              C[row, col] += A[row, elt] * B[elt, col]
    return C
  else:
    return "Помилка: Множення матриць A і B неможливе."
  

Розбір коду функції

Детально розглянемо код нашої функції.

`global C`: За замовчуванням усі змінні в Python мають локальну область видимості. Щоб матриця C була доступна ззовні, оголосимо її як глобальну змінну.

Перевірка можливості множення: Використовуємо атрибут `shape` для перевірки. `A.shape[0]` та `A.shape[1]` повертають кількість рядків і стовпців відповідно. Умова `A.shape[1] == B.shape[0]` гарантує, що множення є можливим. Якщо ця умова вірна, обчислюється матриця добутку, інакше повертається повідомлення про помилку.

Використання вкладених циклів: Обчислення елементів результуючої матриці відбувається за допомогою трьох вкладених циклів. Перший цикл проходить по рядках матриці A, другий – по стовпцях матриці B, а третій використовується для обчислення скалярного добутку.

▶️ Тепер викличемо нашу функцію з матрицями A і B, які ми створили раніше.

multiply_matrix(A,B)
  

Виведе:

[[ 89 107]
 [ 47  49]
 [ 40  44]]
  

Оскільки множення матриць є можливим, функція повертає результат множення – матрицю C.

Використання вкладених спискових включень для множення матриць

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

Ось як виглядає спискове включення для множення матриць:

На перший погляд це може здатися складним. Але ми розберемо це крок за кроком.

Розглянемо структуру спискового включення:

[<операція> for <елемент> in <ітерований об'єкт>]

де:
<операція>: операція, яку потрібно виконати з елементом.
<елемент>: кожен елемент, над яким виконується операція.
<ітерований об'єкт>: список, кортеж або інший ітерований об'єкт, через який ми проходимо.
  

▶️ Ознайомтеся з нашою статтею про спискові включення в Python для глибшого розуміння.

Зауважте, що ми хочемо побудувати матрицю C по одному рядку за раз.

Пояснення вкладеного спискового включення

Крок 1: Обчислення одного елемента матриці C

Для рядка i матриці A і стовпця j матриці B, наступний вираз повертає елемент з індексами (i, j) в матриці C:

sum(a*b for a,b in zip(A_row, B_col))

# zip(A_row, B_col) повертає ітератор кортежів
# Якщо A_row = [a1, a2, a3] & B_col = [b1, b2, b3]
# zip(A_row, B_col) поверне (a1, b1), (a2, b2) і так далі
  

Якщо i = j = 1, цей вираз поверне елемент `c_11` матриці C. Таким чином, можна отримати один елемент в одному рядку.

Крок 2: Створення одного рядка матриці C

Наша наступна задача – створити цілий рядок матриці.

Для рядка 1 матриці A, потрібно пройти по всіх стовпцях матриці B, щоб отримати повний рядок матриці C.

Повернемося до структури спискового включення.

  • Замінимо `<операція>` на вираз з кроку 1.
  • Замінимо `<елемент>` на `B_col` – кожен стовпець матриці B.
  • І `<ітерований об’єкт>` на `zip(*B)` – список, що містить усі стовпці матриці B.

І ось наше перше спискове включення:

[sum(a*b for a,b in zip(A_row, B_col)) for B_col in zip(*B)]

# zip(*B): * - оператор розпакування
# zip(*B) повертає список стовпців матриці B
  

Крок 3: Створення всіх рядків та отримання матриці C

Далі потрібно заповнити матрицю C, обчисливши решту рядків. Для цього потрібно пройти через всі рядки матриці A.

Повернемося до спискового включення знову:

  • Замінимо `<операція>` на спискове включення з кроку 2.
  • Замінимо `<елемент>` на `A_row` – кожен рядок матриці A.
  • `<ітерований об’єкт>` – це матриця A при проходженні по її рядках.

Ось наше кінцеве вкладене спискове включення:🎊

[[sum(a*b for a,b in zip(A_row, B_col)) for B_col in zip(*B)] 
    for A_row in A]
  

Час перевірити результат! ✔

C = np.array([[sum(a*b for a,b in zip(A_row, B_col)) for B_col in zip(*B)] 
    for A_row in A])

print(C)
  

Виведе:

[[ 89 107]
 [ 47  49]
 [ 40  44]]
  

Результат ідентичний результату, отриманому при використанні вкладених циклів `for`, але вираз більш лаконічний.

Далі ви дізнаєтеся про більш ефективні вбудовані функції.

Використання `NumPy matmul()` для множення матриць у Python

Функція `np.matmul()` приймає дві матриці та повертає добуток, якщо множення матриць можливе.

C = np.matmul(A,B)
print(C)
  

Виведе:

[[ 89 107]
 [ 47  49]
 [ 40  44]]
  

Цей метод простіший за попередні. Замість `np.matmul()` можна використовувати еквівалентний оператор `@`, про що ми поговоримо далі.

Використання оператора `@` для множення матриць

У Python оператор `@` використовується для множення матриць.

Він працює з матрицями та N-вимірними масивами NumPy і повертає матрицю добутку.

Примітка: оператор `@` доступний у Python 3.5 та новіших версіях.

Ось приклад його використання:

C = A @ B
print(C)
  

Виведе:

[[ 89 107]
 [ 47  49]
 [ 40  44]]
  

Зверніть увагу, що матриця C ідентична результатам, отриманим раніше.

Чи можна використовувати `np.dot()` для множення матриць?

Якщо ви зустрічали код, де для множення матриць використовується `np.dot()`, то ось як це працює:

C = np.dot(A,B)
print(C)
  

Виведе:

[[ 89 107]
 [ 47  49]
 [ 40  44]]
  

`np.dot(A, B)` повертає очікуваний результат, але, згідно з документацією NumPy, `np.dot()` слід використовувати лише для обчислення скалярного добутку двох одновимірних векторів, а не для множення матриць.

Як згадувалося раніше, елемент C[i, j] – це скалярний добуток i-го рядка матриці A і j-го стовпця матриці B. NumPy неявно перетворює операцію скалярного добутку в усі рядки і стовпці, тому й отримується матриця добутку. Але для кращої читабельності коду слід використовувати `np.matmul()` або оператор `@`.

Висновки

🎯 У цьому посібнику ми розглянули наступні теми:

  • Умову для коректного множення матриць: кількість стовпців матриці A має дорівнювати кількості рядків матриці B.
  • Створення власної функції Python, яка перевіряє можливість множення та повертає матрицю-добуток. Вона використовує вкладені цикли `for`.
  • Множення матриць за допомогою вкладених спискових включень. Вони більш стислі, але менш читабельні.
  • Використання вбудованої функції `NumPy np.matmul()` для множення матриць, як найбільш ефективного методу.
  • Використання оператора `@` для множення матриць в Python.

На цьому наш посібник з множення матриць в Python закінчується. Як наступний крок, ви можете вивчити, як перевірити, чи є число простим, або розв’язати цікаві завдання з рядками Python.

Бажаємо вам успіхів у навчанні!🎉