Содержание страницы
Векторы и матрицы
Здравствуйте и вновь добро пожаловать на занятия по теме «Инструментарий Numpy на языке Python».
В этой статье мы подробнее рассмотрим векторы, а также поговорим о матрицах.
Вы уже видели, насколько массивы Numpy похож на векторы: мы можем проводить над ними такие операции, как их сложение, умножение на скаляр, выполнять поэлементные операции вроде возведения в квадрат. А что с матрицами? Матрицу можно рассматривать как двухмерный массив. Согласно другому представлению, её также можно рассматривать как список списков. Действительно, можно использовать список списков, чтобы определить матрицу. Попробуем так и сделать.
Итак,первый список будет иметь элементы 1 и 2, второй список – элементы 3 и 4.Обратите внимание, что списки должны иметь одинаковую длину.
M = np.array([ [1,2], [3,4] ])
Считается,что первый индекс – это строка, второй – столбец. Для сравнения создадим такженастоящий список списков:
L = [ [1,2], [3,4] ]
Допустим,мы хотим получить элемент матрицы, скажем, единицу. В списке Python сначала индексируется строка, что даёт нам первыйсписок, содержащий 1 и 2:
L[0]
Итак,теперь у нас есть 1 и 2. Нам нужен первый элемент из этого списка, поэтому
L[0][0]
и получаем 1. Заметьте, то же самое можно сделать ис помощью массива Numpy:
M[0][0]
Этотакже даёт нам 1. Но есть и сокращённая запись, похожая на MATLAB, с использованием запятой:
M[0,0]
Иэто также даёт нам 1. Так несколько удобнее, поскольку нужно набрать на символменьше.
Обратитевнимание, что в Numpy есть типданных, который так и называется – матрица:
M2 = np.matrix([ [1,2], [3,4] ])
Получиласьматрица. Во многом матрица схожа с массивом Numpy,но есть и отличия. В большинстве случаев используются просто массивы Numpy; в действительности даже официальная документацияфактически рекомендует не пользоваться матрицами. Поэтому в данном курсе мы небудем заострять на них своё внимание. Увидев матрицу, имеет смысл преобразоватьеё в массив. Это можно сделать с помощью команды
A = np.array(M2)
Врезультате получим ту же матрицу, но в виде массива. Обратите внимание, что,хоть это и массив, мы можем пользоваться удобными матричными операциями, напримертранспонированием:
A.T
Врезультате получается транспонированная матрица A.
Резюмируем. Мы показали, что матрица на самом деле является лишь двухмерным массивом Numpy, а вектор – одномерным массивом Numpy. Таким образом, матрица на самом деле является двухмерным вектором. Обобщая, можно считать матрицу двухмерным математическим объектом, содержащим числа, а вектор – одномерным математическим объектом, также содержащим числа. Иногда встречается представление вектора в виде двухмерного объекта. Например, в учебниках по математике может указываться вектор-столбец размерности 3×1 и вектор-строка размерности 1×3. Временами мы также будем представлять их в таком виде в Numpy, но наличие двух размерностей делает такой объект более похожим на матрицу, что может сбить с толку. В таком случае просто помните, что обсуждаются лишь две вещи – одномерные и двухмерные массивы.
Создание матриц для работы с ними
Существуют некоторые различные способы генерации массивов данных.
Иногда нужны несколько массивов просто чтобы попрактиковаться – как в этом курсе. Один из способов вы уже видели, когда я воспользовался массивом Numpy для создания списка, например
np.array([1,2,3])
Заметьте,это не очень удобно, поскольку приходится вручную вставлять каждый элемент. Аесли нужен массив с сотней элементов? А если нужно, чтобы он содержал случайныечисла? Поэтому в данной лекции будет показано, как можно создавать массивы.
Преждевсего я покажу, как создать массив, состоящих из одних нулей. Это делается спомощью функции zeros с указаниемдлины:
Z = np.zeros(10)
Этодаёт вектор с 10 элементами, состоящий из нулей. Можно создать и матрицуразмерности 10×10, состоящую из одних нулей:
Z = np.zeros((10, 10))
Врезультате получаем 100 нулей в матрице размерности 10×10.Обратите внимание, что функция по-прежнему принимает лишь один аргумент – кортеж,содержащий каждую размерность. Есть эквивалентная функция, создающая массив изодних единиц. К примеру,
O = np.ones((10, 10))
Получиласьматрица размерности 10×10, состоящая изодних единиц.
Теперь предположим, что нам нужен ряд случайных числен. Для этого можно воспользоваться функцией np.random.random. Создадим, к примеру, массив случайных чисел размерности 10×10:
R = np.random.random((10,10))
Получилсянабор случайных чисел в матрице размерностью 10×10.Бросается в глаза, что все числа больше 0 и меньше 1. Это связано с тем, чтокогда речь идёт о случайных числах, имеется в виду распределение вероятностей,откуда и возникли случайные числа. Данная конкретная функция случайных чиселдаёт нам равномерно распределённые числа между 0 и 1. А если нам нужны числа сгауссовым распределением? В Numpy есть функция идля этого. Давайте попробуем. Называется она random.randn. Вновь-таки, возьмём размерность 10×10:
G = np.random.randn((10×10))
И у нас ничего не получилось – требуются целые числа. Сама функция правильна, но мы указали неправильный аргумент. Дело в том, что, как ни странно, функция randn библиотеки Numpy воспринимает каждую размерность как отдельный аргумент, в то время как все остальные упоминавшиеся функции принимают кортежи. Поэтому правильно вставлять – только для функции randn! – каждую размерность по отдельности:
G = np.random.randn(10,10)
Теперьвсё работает. Мы имеем числа с гауссовым распределением, средним значением 0 идисперсией 1. Массивы Numpy такжепредоставляют удобные функции для вычисления этих статистических величин. Так,команда
G.mean()
даёт нам среднее значение, а команда
G.var()
позволяет получить дисперсию.
Как видим, полученные числа весьма близки к истинным значениям.
Произведение матриц
Чтоинтересно в произведениях матриц при изучении линейной алгебры, – так это то, чтообычно называется умножением матриц. Умножение матриц имеет специальноетребование: внутренние размерности двух умножаемых матриц должны совпадать.Так, если у нас есть матрица A размерности 2×3и матрица Bразмерности 3×3, то мы можем умножить A на B, поскольку внутренняя размерность равна 3. Однакомы не можем умножить B на A, поскольку внутренняя размерность левого множителяравна 3, а внутренняя размерность правого равна 2.
Возникаетвопрос: для чего существует такое требование при умножении матриц? Рассмотримопределение операции умножения матриц:

Тоесть ij-йэлемент матрицы Cравен сумме произведений всех соответствующих элементов из i-й строкиматрицы Aи j-гостолбца матрицы B.Другими словами, ij-йэлемент матрицы Cравен скалярному произведению i-й строки матрицы A и j-го столбца матрицы B. В связи с этим обычно используется функция dot библиотеки Numpy:
C = A.dot(B)
Этои будет умножением матриц.
Как в математике, так и в программировании часто встречается операция поэлементного умножения. В случае векторов мы уже видели, что для этого используется звёздочка (*). Как можно догадаться, в случае двухмерных массивов звёздочка также производит поэлементное умножение. Это означает, что при использовании звёздочки в случае многомерных массивов оба массива должны иметь в точности одинаковую размерность. Это может показаться странным, ведь в других языках звёздочка действительно означает настоящее умножение матриц. Но нужно просто запомнить, что для Numpy звёздочка означает поэлементное умножение, а функция dot – умножение матриц. Также сбивать с толку может то обстоятельство, что при записи математических уравнений нет даже чётко определённого символа для операции поэлементного умножения. Так, некоторые пользуются кружком с точкой внутри, а некоторые – кружком с крестиком внутри. Похоже, в математике просто не существует стандартной записи для данной операции, хотя она часто возникает в машинному обучении в связи с необходимостью использовать градиенты.
Другие операции с матрицами
В данном подразделе, я покажу вам некоторые другие операции с матрицами, которые позволяет библиотека Numpy. При этом предполагается, что вы уже знакомы с ними из курса линейной алгебры, а потому это будет скорее демонстрация того, как их реализовать в Numpy.
Начнёмс нахождения обратной матрицы. Вначале создадим матрицу:
A = np.array([[1,2],[3,4]])
Воспользуемсяфункцией inv модуля linalg:
Ainv = np.linalg.inv(A)
Мыполучили обратную матрицу. Чтобы проверить правильность ответа, умножим матрицу,обратную A,на саму А:
Ainv.dot(A)
Врезультате имеем единичную матрицу. Можем сделать и наоборот, умножив матрицу А на обратную ей:
A.dot(Ainv)
Итоже получаем единичную матрицу.
Следующее– нахождение определителя матрицы. Это можно сделать с помощью команды
np.linalg.det(A)
Каки ожидалось, получаем ответ -2.
Иногданужна диагональ матрицы, для этого используется команда
np.diag(A)
Врезультате имеем диагональные элементы в виде вектора. В других случаях у насесть вектор чисел, представляющий диагональ матрицы, остальные элементы которойсчитаются равными нулю. Чтобы представить такой вектор в виде двухмерногомассива, можно воспользоваться этой же функцией. Например,
np.diag([1,2])
Получим1 и 2 на диагонали, а остальные элементы – нули.
Этонадо запомнить: если подставить двухмерный массив в функцию diag, то получим одномерный массив из диагональныхэлементов; если же подставить одномерный массив, то получится двухмерныймассив, в котором все внедиагональные элементы равны нулю, а первоначальныймассив располагается на главной диагонали матрицы.
Бывает, что у нас есть два вектора, а нам нужно выполнить внешнее произведение. В частности, внешнее произведение возникает, когда мы вычисляем ковариацию ряда векторов-образцов. Мы уже знакомы с поэлементным произведением, для которого используется звёздочка, и скалярным произведением, для чего используется функция dot. Напомню, что скалярное произведение также называется внутренним произведением.
Итак,создадим два вектора:
a = np.array([1,2])
b = np.array([3,4])
Внешнеепроизведение вычисляется по команде
np.outer(a, b)
Можетепроверить правильность ответа. Обратите внимание, что можно выполнить ивнутреннее произведение с помощью команды
np.inner(a, b)
Этодаст тот же ответ, что и при использовании команды
a.dot(b)
Какимиз этих способов пользоваться – сугубо на ваше усмотрение.
Другаяраспространённая операция – нахождение следа матрицы. Это сумма элементовматриц, расположенных на главной диагонали. Заметьте, что мы можем выполнитьэту операцию, используя уже имеющиеся сведения:
np.dialog(A).sum()
Получиможидаемый ответ 5. Но в Numpy для этого естьи соответствующая функция:
np.trace(A)
Имы опять-таки получаем 5.
Ипоследнее, что мы обсудим, – это собственные значения и собственные векторы.Если вы не проходили их в курсе линейной алгебры, то, вероятно, вам следуетуглубить свои знания по этому предмету. Но сейчас я просто покажу, как всё это делаетсяв коде, поэтому если вы не знаете, что такое собственные значения и собственныевекторы, просто пропустите эту часть.
Частовозникает необходимость вычислить собственные значения и собственные векторысимметричной матрицы, такой как ковариационная матрица набора данных. Создадимслучайным образом определённые данные размерности 100×3с гауссовым распределением:
X = np.random.randn(100,3)
Обратитевнимание: считается, что каждый пример – это строка, а каждый признак – этостолбец, поэтому для данного конкретного вымышленного набора данных у насимеется 100 примеров и 3 признака. Разумеется, в Numpyуже есть функция для вычисления ковариации. Опробуем её:
cov = np.cov(X)
Проверимразмерность ковариационной матрицы, чтобы убедиться, что всё правильно.
cov.shape
Получилось,что размерность равна 100×100. Этонеправильная размерность, должно быть 3×3,ведь наши данные имеют размерность 3. Попробуем ещё раз, транспонировав X:
cov = np.cov(X.T)
Теперьразмерность 3×3. Нужно помнить, что при вычислении ковариационнойматрицы данных необходимо её сначала транспонировать.
Длявычисления собственных значений и собственных векторов есть две функции – eig и eigh. Eigh предназначена только для симметричных и эрмитовыхматриц. Если вы никогда не изучали линейную алгебру комплексных чисел, небеспокойтесь о том, что такое эрмитова матрица. Симметричной называетсяматрица, которая, будучи транспонированной, остаётся равной сама себе:
У вас дублирование текста.