Множення матриць у 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.
Бажаємо вам успіхів у навчанні!🎉