пятница, 13 ноября 2020 г.

NumPy: матрицы и операции над ними

Создание матриц

Приведем несколько способов создания матриц в NumPy

Самый простой способ — с помощью функции numpy.array(list,dtype=None,...)

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

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

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

import numpy as np

a = np.array([[1, 2, 3], [2, 5, 6], [6, 7, 4]])
print("Матрица:\n", a)

Матрица:
 [[1 2 3]
 [2 5 6]
 [6 7 4]]

Второй способ создания — с помощью встроенных функций numpy.eye(N,M=None,...),

numpy.zeros(shape,...),numpy.ones(shape,...)

Первая функция создает единичную матрицу размера N×M; если M не задан, то M=N.

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

Примеры:

b = np.eye(5)
print("Единичная матрица:\n", b)

Единичная матрица:
 [[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]

c = np.ones((7, 5))
print("Матрица, состоящая из одних единиц:\n", c)

Матрица, состоящая из одних единиц:
 [[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]

Обратить внимание: размерность массива задается не двумя аргументами функции, а одним — кортежем!

Вот так — np.ones(7, 5) — создать массив не получится, так как функции в качестве
параметра shape передается 7 , а не кортеж (7,5)


Третий способ - с помощью функции numpy.arange([start, ],stop, [step, ],...) 
которая   создает одномерный массив последовательных чисел из 
промежутка [start, stop]  с заданным шагом  step и метода array.reshape(shape)

Параметр shape, как и в предыдущем примере, задает размерность матрицы
  (кортеж чисел). Логика работы метода ясна из следующего примера:

v = np.arange(0, 24, 2)
print("Вектор-столбец:\n", v)

Вектор-столбец:
 [ 0  2  4  6  8 10 12 14 16 18 20 22]

d = v.reshape((3, 4))
print("Матрица:\n", d)

Матрица:
 [[ 0  2  4  6]
 [ 8 10 12 14]
 [16 18 20 22]]

Индексирование


Для получения элементов матрицы можно использовать несколько способов. 
Рассмотрим самые простые из них.

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

print("Матрица:\n", d)

Матрица:
 [[ 0  2  4  6]
 [ 8 10 12 14]
 [16 18 20 22]]

Элемент на пересечении строки i и столбца j можно получить с помощью
выражения array(i,j)

Обратить внимание: строки и столбцы нумеруются с нуля!

print("Второй элемент третьей строки матрицы:", d[2, 1])
Второй элемент третьей строки матрицы: 18

Из матрицы можно получать целые строки или столбцы с помощью выражений 
array(i,:) или array(:,j) соответственно :

print("Вторая строка матрицы d:\n", d[1, :])
print("Четвертый столбец матрицы d:\n", d[:, 3])
Вторая строка матрицы d:
 [ 8 10 12 14]
Четвертый столбец матрицы d:
 [ 6 14 22]


Еще один способ получения элементов — с помощью выражения array(list1,list2) где 
list1,list2 некоторые списки целых чисел. При такой адресации одновременно
 просматриваются оба списка и возвращаются элементы матрицы с соответствующими 
координатами. Следующий пример более понятно объясняет механизм работы такого 
индексирования:

print("Элементы матрицы d с координатами (1, 2) и (0, 3):\n", d[[1, 0], [2, 3]])
Элементы матрицы d с координатами (1, 2) и (0, 3):
 [12  6]

Векторы, вектор-строки и вектор-столбцы


Следующие два способа задания массива кажутся одинаковыми:

a = np.array([1, 2, 3])
b = np.array([[1], [2], [3]])

Однако, на самом деле, это задание одномерного массива (то есть _вектора_) и  
двумерного массива:

print("Вектор:\n", a)
print("Его размерность:\n", a.shape)
print("Двумерный массив:\n", b)
print("Его размерность:\n", b.shape)

Вектор:
 [[1 2 3]
 [2 5 6]
 [6 7 4]]
Его размерность:
 (3, 3)
Двумерный массив:
 [[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]
Его размерность:
 (5, 5)

Обратить внимание: вектор (одномерный массив) и вектор-столбец или
  вектор-строка (двумерные массивы) являются различными объектами в NumPy
хотя математически задают один и тот же объект. В случае одномерного массива кортеж
  shape состоит из одного числа и имеет вид (n,) , где n — длина вектора. В случае
 двумерных векторов в shape  присутствует еще одна размерность, равная единице.

В большинстве случаев неважно, какое представление использовать, потому что часто
 срабатывает приведение типов. Но некоторые операции не работают для одномерных
массивов. Например, транспонирование (о нем пойдет речь ниже):

a = a.T
b = b.T

print("Вектор не изменился:\n", a)
print("Его размерность также не изменилась:\n", a.shape)
print("Транспонированный двумерный массив:\n", b)
print("Его размерность изменилась:\n", b.shape)

Вектор не изменился:
 [[1 2 6]
 [2 5 7]
 [3 6 4]]
Его размерность также не изменилась:
 (3, 3)
Транспонированный двумерный массив:
 [[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]
Его размерность изменилась:
 (5, 5)

Умножение матриц и столбцов


Операция умножения определена для двух матриц, таких что число столбцов первой
  равно числу строк второй.
В NumPy произведение матриц вычисляется с помощью функции numpy.dot(a,b,...) или
с помощью метода array1.dot(array2), где array1 и array2 - перемножаемые матрицы.

a = np.array([[1, 0], [0, 1]])
b = np.array([[4, 1], [2, 2]])
r1 = np.dot(a, b)
r2 = a.dot(b)

print("Матрица A:\n", a) 
print("Матрица B:\n", b) 
print("Результат умножения функцией:\n", r1) 
print("Результат умножения методом:\n", r2)

Матрица A:
 [[1 0]
 [0 1]]
Матрица B:
 [[4 1]
 [2 2]]
Результат умножения функцией:
 [[4 1]
 [2 2]]
Результат умножения методом:
 [[4 1]
 [2 2]]

Матрицы в NumPy можно умножать и на векторы:

c = np.array([1, 2])
r3 = b.dot(c)

print("Матрица:\n", b) 
print("Вектор:\n", c) 
print("Результат умножения:\n", r3)

Матрица:
 [[4 1]
 [2 2]]
Вектор:
 [1 2]
Результат умножения:
 [6 6]

Обратить внимание: операция * производит над матрицами покоординатное 
умножение, а не матричное!

r = a * b

print("Матрица A:\n", a) 
print("Матрица B:\n", b) 
print("Результат покоординатного умножения через операцию *:\n", r)

Матрица A:
 [[1 0]
 [0 1]]
Матрица B:
 [[4 1]
 [2 2]]
Результат покоординатного умножения через операцию *:
 [[4 0]
 [0 2]]

Транспонирование матриц


Транспонированной матрицей AT называется матрица, полученная из исходной

 матрицы A заменой строк на столбцы. 

В NumPy транспонирование матриц вычисляется с помощью функции

 numpy.transpose() или с помощью метода array.T, где array - двумерный массив.

a = np.array([[1, 2], [3, 4]])
b = np.transpose(a)
c = a.T

print("Матрица:\n", a) 
print("Транспонирование функцией:\n", b) 
print("Транспонирование методом:\n", c)


Матрица:
 [[1 2]
 [3 4]]
Транспонирование функцией:
 [[1 3]
 [2 4]]
Транспонирование методом:
 [[1 3]
 [2 4]]


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


Для квадратных матриц существует понятие определителя.

Вычисление определителя матрицы по определению требует порядка n! операций,
поэтому разработаны методы, которые позволяют вычислять его быстро и эффективно.

В NumPy определитель матрицы вычисляется с помощью функции numpy.linalg.det(a), где a - исходная матрица.

a = np.array([[1, 2, 1], [1, 1, 4], [2, 3, 6]], dtype=np.float32)
det = np.linalg.det(a)

print("Матрица:\n", a) 
print("Определитель:\n", det)

Матрица:
 [[1. 2. 1.]
 [1. 1. 4.]
 [2. 3. 6.]]
Определитель:
 -1.0

Ранг матрицы


Рангом матрицы A называется максимальное число линейно независимых строк
 (столбцов) этой матрицы.


В NumPy ранг матрицы вычисляется с помощью функции
 numpy.linalg.rank(M,tol=None), где M - матрица, tol - параметр, 

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

не задавать и функция сама определит подходящее значение этого

параметра. 


a = np.array([[1, 2, 3], [1, 1, 1], [2, 2, 2]])
r = np.linalg.matrix_rank(a)

print("Матрица:\n", a) 
print("Ранг матрицы:", r)

Матрица:
 [[1 2 3]
 [1 1 1]
 [2 2 2]]
Ранг матрицы: 2

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

a = np.array([1, 2, 3])
b = np.array([1, 1, 1])
c = np.array([2, 3, 5])
m = np.array([a, b, c])

print(np.linalg.matrix_rank(m) == m.shape[0])

Системы линейных уравнений


Системой линейных алгебраических уравнений называется система вида Ax=b
В случае квадратной невырожденной матрицы 
A решение системы единственно.

В NumPy решение такой системы можно найти с помощью функции
  numpy.linalg.solve(a,b) , где первый аргумент - матрица A, второй - столбец b.

a = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = np.linalg.solve(a, b)

print("Матрица A:\n", a) 
print("Вектор b:\n", b) 
print("Решение системы:\n", x)

Матрица A:
 [[3 1]
 [1 2]]
Вектор b:
 [9 8]
Решение системы:
 [2. 3.]

Убедимся, что вектор x действительно является решением системы:

print(a.dot(x))
[9. 8.]

Бывают случаи, когда решение системы не существует. Но хотелось бы все равно
  "решить" такую систему. Логичным кажется искать такой вектор x , который
  минимизирует выражение ||Ax-b||^2 - так мы приблизим выражение Ax к b.

В NumPy такое псевдорешение  можно искать с помощью функции 
 numpy.linalg.lstsq(a,b,...), где первые два аргумента такие же как и для функции
 numpy.linalg.solve(). Помимо решения функция возвращает еще три значения

a = np.array([[0, 1], [1, 1], [2, 1], [3, 1]])
b = np.array([-1, 0.2, 0.9, 2.1])
x, res, r, s = np.linalg.lstsq(a, b)

print("Матрица A:\n", a) 
print("Вектор b:\n", b) 
print("Псевдорешение системы:\n", x)

Матрица A:
 [[0 1]
 [1 1]
 [2 1]
 [3 1]]
Вектор b:
 [-1.   0.2  0.9  2.1]
Псевдорешение системы:
 [ 1.   -0.95]

Обращение матриц


Для квадратных невырожденных матриц определено понятие обратной матрицы.
Пусть A — квадратная невырожденная матрица. Матрица A* называется обратной
 матрицей к A, если AA*=A*A=I, где I - единичная матрица.

В NumPy обратные матрицы вычисляются  с помощью функции numpy.linalg.inv(a), 
где a - исходная матрица.


a = np.array([[1, 2, 1], [1, 1, 4], [2, 3, 6]], dtype=np.float32)
b = np.linalg.inv(a)

print("Матрица A:\n", a) 
print("Обратная матрица к A:\n", b) 
print("Произведение A на обратную должна быть единичной:\n", a.dot(b))

Матрица A:
 [[1. 2. 1.]
 [1. 1. 4.]
 [2. 3. 6.]]
Обратная матрица к A:
 [[ 6.  9. -7.]
 [-2. -4.  3.]
 [-1. -1.  1.]]
Произведение A на обратную должна быть единичной:
 [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Собственные числа и собственные вектора матрицы


Для квадратных матриц определены понятия собственного вектора и 
собственного числа.

В NumPy собственные числа и собственные вектора вычисляются  с помощью
функции numpy.linalg.eig(a), a - исходная матрица. В качестве результата эта функция 
выдает одномерный массив w собственных чисел и двумерный массив v, в котором по 
столбцам записаны собственные вектора, так что вектор v[:,i] соответствует
 собственному числу w[i].

a = np.array([[-1, -6], [2, 6]])
w, v = np.linalg.eig(a)

print("Матрица A:\n", a)
print("Собственные числа:\n", w)
print("Собственные векторы:\n", v)

Матрица A:
 [[-1 -6]
 [ 2  6]]
Собственные числа:
 [2. 3.]
Собственные векторы:
 [[-0.89442719  0.83205029]
 [ 0.4472136  -0.5547002 ]]

Обратите внимание: у вещественной матрицы собственные значения или собственные векторы могут быть комплексными.






























Комментариев нет:

Отправить комментарий