Массив Numpy

Списки и массивы

Здравствуйте и вновь добро пожаловать на занятия по теме «Инструментарий Numpy на языке Python».

В этой статье мы сравним списки и массивы, и разберем скалярное произведение.

Первое,что приходит в голову, – а какой смысл использовать массив Numpy? Разве он не похож в точности на список? Давайтезапустим Ipython и выполним несколько примеров.

ИмпортируемNumpy и создадим список, а затем – такой же массив:

import numpy as np

L = [1,2,3]

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

Обаони дают одно и тоже – объект с тремя элементами 1, 2 и 3. Часто встречающаязадача – пройтись по списку. Так и сделаем:

for ein L:

    print e

Всёработает. Теперь то же самое, но с массивом:

for e in A:

    print e

Тожеработает нормально. Итак, работают они одинаково, так что остаётся вопрос, вчём смысл?

Допустим,нам нужно что-то добавить в наш список. В случае обычного списка используетсякоманда append:

L.append(4)

ТеперьL содержит ещё и элемент 4. Но что будет, сделать тоже самое с массивом?

A.append(4)

Оказывается,нет метода append для массива Numpy.

Ноесть ещё один способ добавить элемент в список – объединить два списка. Сначалапопробуем со списком:

L = L + [5]

ТеперьL содержит и 5. Теперь попробуем это же с Numpy. Попробуем добавить 4 и 5, поскольку в массиве ещёнет четвёрки:

A = A + [4,5]

Иэто не работает. Похоже, что мы вообще не можем ничего добавить к массиву Numpy. Вероятно, в этот момент вы решили, что Numpy – дрянь, ведь он не может даже того, что можетсписок. Но давайте попробуем кое-что ещё. Давайте попробуем сложение векторов.Чтобы было совсем просто, просто добавим вектор к самому себе. В случаем спискаPython мы знаем, что использование знака «плюс» простодаёт нам новый список с объединением всех элементов. Но нам нужно не это. Намнужно складывать каждый элемент по отдельности.

Итак,создадим пустой список и пройдёмся по каждому элементу eсписка L, добавляя e к e:

L2 = []

for e in L:

    L2.append(e+ e)

Витоге L2 будет равно L+ L. А как это сделать в Numpy?Попробуем

A + A

Ив результате получаем именно то, что нужно. Итак, урок первый: знак «плюс» вслучае списка приводит к добавлению, а знак «плюс» в массиве Numpy даёт векторное сложение. Если бы у нас быламатрица, которая представляет собой двухмерный массив, то у нас получилось бысложение матриц. Обобщая понятие, можно сказать, что происходит поэлементноесложение.

Какойещё есть способ удвоения вектора без сложения его с самим собой? Ну, его можнопросто умножить на 2, то есть скалярную величину умножить на вектор. Сначалапопробуем с массивом Numpy.

2*A

Чтодаёт нам ожидаемое. Теперь попробуем список.

2*L

Интересно:содержимое списка было просто повторено два раза. Но то обстоятельство, чтополучился другой результат, не значит, что это бесполезная операция. Вдействительности я довольно часто пользуюсь умножением числа на список. Если женужно умножить на число каждый элемент списка, придётся использовать цикл for, как мы сделали в случае L+ L.

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

L**2

Неработает. Мы не можем список возвести в степень. Вернее, можем, но придётсяиспользовать цикл for:

L2 = []

for e in L:

   L2.append(e*e)

ТеперьL2 содержит ожидаемые элементы. Попробуем теперь Numpy:

A**2

Каки ожидалось, всё работает, но куда проще.

Поповоду Numpy нужно помнить, что большинство функций работаютпоэлементно, как мы убедились в случае возведения во вторую степень. А как насчётизвлечения квадратного корня?

np.sqrt(A)

Врезультате извлекается квадратный корень из каждого элемента вектора. Какнасчёт логарифма?

np.log(A)

Берётсялогарифм каждого элемента. А что с экспонентой?

np.exp(A)

Получимпоэлементное экспонирование.

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

Подведём итог. На этой лекции мы узнали, что если нужно представить вектор, то массив Numpy несколько более удобен, поскольку при действиях с ним мы получаем ожидаемый результат, когда применяем операции вроде сложения, умножения и возведения в квадрат. Списки тоже неплохи – во многих случаях на самом деле нам нужен именно список, а не массив Numpy. Обычно список можно рассматривать как массив, но массив Numpy можно рассматривать как вектор – математический объект. Для выполнения операций над списками приходится использовать цикл for, а как вы увидите позже, цикл for в Python очень медлителен, так что следует всячески его избегать при выполнении математических операций.

Скалярное произведение, часть 1. Цикл for, метод косинусов, функция dot

Здесь мы обсудим скалярное произведение – вид умножения, который можно применить по отношению к векторам. Напомню, что есть два эквивалентных определения скалярного произведения. Первое – это сумма поэлементных произведений двух векторов. Если использовать d в качестве индекса каждого компонента, то получится

Обратитевнимание, что запись aTb подразумевает, что по умолчанию векторысчитаются векторами-столбцами. Следовательно, результат будет иметь размерность1×1 – то есть это скалярная величина.

Второеопределение – это модуль a, умноженный на модуль b и умноженный на косинус угла между a и b:

Обратитевнимание, что запись aTb подразумевает, что по умолчанию векторысчитаются векторами-столбцами. Следовательно, результат будет иметь размерность1×1 – то есть это скалярная величина.

Второеопределение – это модуль a, умноженный на модуль b и умноженный на косинус угла между a и b:

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

Этовведение в лекцию очень удачно позволит нам изучить больше возможностей Numpy. Сделаем это прямо в коде.

Начнёмс двух векторов:

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

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

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

dot = 0

for e,f inzip(a,b)

    dot += e*f

Ответ4, что и ожидалось. Но над массивами Numpy можнопроизвести ещё одну интересную операцию – перемножение двух массивов. Ранее мывидели только умножение вектора на скалярную величину. Попробуем умножить a и bи посмотрим, что получится

a*b

Мывидим, что это, как и следовало ожидать, дало нам поэлементное умножение двухмассивов. Это также означает, что нельзя выполнить такую операцию над двумямассивами, имеющими разные длины. Сейчас же нам нужно лишь просуммировать всеэлементы. К счастью, Numpy имеет функцию sum, позволяющую это сделать:

np.sum(a*b)

Этодаёт нам ожидаемый результат. Интересно, что функция sumявляется методом экземпляра самого массива Numpy,так что в качестве альтернативы можно использовать команду

(a*b).sum()

Этодаёт нам тот же результат.

Наконец,в то время как оба эти способа дают правильный ответ, есть и ещё более удобныйспособ вычисления скалярного произведения с помощью функции dot:

np.dot(a, b)

Иэто вновь-таки даёт нам ожидаемый результат. Как и функция sum, функция dot также являетсяметодом экземпляра массива Numpy, так что мыможем вызвать её для самого объекта:

a.dot(b)

Иполучаем всё тот же ответ. Это также эквивалентно команде

a.dot(b)

Теперьвоспользуемся альтернативным определением скалярного произведения, чтобывычислить угол между a и b. Для этого надовыяснить, как рассчитать длину вектора. Мы можем это сделать, пользуясь лишьимеющимися данными – длина равна квадратному корню, извлечённому из суммыквадратов каждого элемента. Так, например, для a имеем:

amag = np.sqrt( (a*a).sum() )

Каки ожидалось, длина вектора а равна2,236. На самом деле в Numpy для этогоимеется специальная функция, поскольку это достаточно распространённаяоперация. Функция является частью модуля linalgбиблиотеки Numpy, в состав которого входят также множество другихраспространённых функций линейной алгебры. Попробуем ещё раз с помощью linalg:

amag = np.linalg.norm(a)

Иполучаем тот же ответ. Итак, теперь мы готовы вычислить угол:

cosangle = a.dot(b) / (np.linalg.norm(a) *np.linalg.norm(b) )

Итак,косинус угла равен 0,8. А сам угол равен

angle = np.arccos(cosangle)

Итак, угол равен 0,643 – по умолчанию, ответ даётся в радианах.

Скалярное произведение, часть 2. Сравнение скорости работы

Теперь нам нужно сравнить два способа вычисления скалярного произведения и посмотрим, сколько времени займёт их выполнение.

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

Итак, запустим файл. Он называется dot_for.py.

Запустимещё раз, чтобы убедиться, что это не случайность.

Итак,при первом запуске способ с Numpy оказался в 37раз быстрее, при втором – в 25 раз быстрее, при третьем – в приблизительно 33раза быстрее. Результат всегда будет где-то в этом диапазоне. Как видите,функция dot библиотеки Numpyработает на несколько порядков быстрее, чем цикл for.Отсюда урок: не пользуйтесь циклом for, если этогоможно избежать.

Этовведение в лекцию очень удачно позволит нам изучить больше возможностей Numpy. Сделаем это прямо в коде.

Начнёмс двух векторов:

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

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

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

dot = 0

for e,f inzip(a,b)

    dot += e*f

Ответ4, что и ожидалось. Но над массивами Numpy можнопроизвести ещё одну интересную операцию – перемножение двух массивов. Ранее мывидели только умножение вектора на скалярную величину. Попробуем умножить a и bи посмотрим, что получится

a*b

Мывидим, что это, как и следовало ожидать, дало нам поэлементное умножение двухмассивов. Это также означает, что нельзя выполнить такую операцию над двумямассивами, имеющими разные длины. Сейчас же нам нужно лишь просуммировать всеэлементы. К счастью, Numpy имеет функцию sum, позволяющую это сделать:

np.sum(a*b)

Этодаёт нам ожидаемый результат. Интересно, что функция sumявляется методом экземпляра самого массива Numpy,так что в качестве альтернативы можно использовать команду

(a*b).sum()

Этодаёт нам тот же результат.

Наконец,в то время как оба эти способа дают правильный ответ, есть и ещё более удобныйспособ вычисления скалярного произведения с помощью функции dot:

np.dot(a, b)

Иэто вновь-таки даёт нам ожидаемый результат. Как и функция sum, функция dot также являетсяметодом экземпляра массива Numpy, так что мыможем вызвать её для самого объекта:

a.dot(b)

Иполучаем всё тот же ответ. Это также эквивалентно команде

a.dot(b)

Теперьвоспользуемся альтернативным определением скалярного произведения, чтобывычислить угол между a и b. Для этого надовыяснить, как рассчитать длину вектора. Мы можем это сделать, пользуясь лишьимеющимися данными – длина равна квадратному корню, извлечённому из суммыквадратов каждого элемента. Так, например, для a имеем:

amag = np.sqrt( (a*a).sum() )

Каки ожидалось, длина вектора а равна2,236. На самом деле в Numpy для этогоимеется специальная функция, поскольку это достаточно распространённаяоперация. Функция является частью модуля linalgбиблиотеки Numpy, в состав которого входят также множество другихраспространённых функций линейной алгебры. Попробуем ещё раз с помощью linalg:

amag = np.linalg.norm(a)

Иполучаем тот же ответ. Итак, теперь мы готовы вычислить угол:

cosangle = a.dot(b) / (np.linalg.norm(a) *np.linalg.norm(b) )

Итак,косинус угла равен 0,8. А сам угол равен

angle = np.arccos(cosangle)

Итак, угол равен 0,643 – по умолчанию, ответ даётся в радианах.

Скалярное произведение, часть 2. Сравнение скорости работы

Теперь нам нужно сравнить два способа вычисления скалярного произведения и посмотрим, сколько времени займёт их выполнение.

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

Итак, запустим файл. Он называется dot_for.py.

Запустимещё раз, чтобы убедиться, что это не случайность.

Итак,при первом запуске способ с Numpy оказался в 37раз быстрее, при втором – в 25 раз быстрее, при третьем – в приблизительно 33раза быстрее. Результат всегда будет где-то в этом диапазоне. Как видите,функция dot библиотеки Numpyработает на несколько порядков быстрее, чем цикл for.Отсюда урок: не пользуйтесь циклом for, если этогоможно избежать.

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: