Операции над векторами в numpy

Рассмотрим некоторые общие функции линейной алгебры и их применение на чистом Python и numpy. Все примеры — на Jupyter Notebook.

Списки в Python не являются векторами, по умолчанию над ними нельзя производить поэлементные операции.

В Python необходимо определять собственные функции, чтобы оперировать списками как векторами. Для сравнения: в numpy для аналогичных операций достаточно одной строки кода.

Сложение векторов

In [1]:

python_list_1 = [40, 50, 60]
python_list_2 = [10, 20, 30]
python_list_3 = [35, 5, 40]

# Ожидаемый результат сложения векторов: [50, 70, 90]
# Но операция сложения двух списков возвращает объединенный список

added_list = python_list_1 + python_list_2
added_list

Out[1]:

[40, 50, 60, 10, 20, 30]

In [2]:

def add_vectors(v, w):
    return [vi + wi for vi, wi in zip(v, w)]

def sum_of_all_vectors(vecs):
    return reduce(add_vectors, vecs)

In [3]:

add_vectors(python_list_1, python_list_2)

Out[3]:

[50, 70, 90]

In [4]:

sum_of_all_vectors([python_list_1, python_list_2, python_list_3])

Out[4]:

[85, 75, 130]

Но, конечно, в numpy это можно сделать с помощью оператора + на numpy-массивах или с помощью метода sum().

In [5]:

import numpy as np
numpy_array_1 = np.array([40, 50, 60])
numpy_array_2 = np.array([10, 20, 30])
numpy_array_3 = np.array([35, 5, 40])

numpy_array_1 + numpy_array_2

Out[5]:

array([50, 70, 90])

In [6]:

np.sum([numpy_array_1, numpy_array_2, numpy_array_3])

Out[6]:

array([ 85,  75, 130])

Вычитание векторов

In [7]:

def subtract_vectors(v, w):
    return [vi - wi for vi, wi in zip(v, w)]

In [8]:

# [40 - 35, 50 - 5, 60 - 40]
subtract_vectors(python_list_1, python_list_3)

Out[8]:

[5, 45, 20]

In [9]:

numpy_array_1 - numpy_array_3

Out[9]:

array([ 5, 45, 20])

Скалярное умножение

In [10]:

def scalar_multiply(c, v):
    return [c * vi for vi in v]

In [11]:

scalar_multiply(10, python_list_1)

Out[11]:

[400, 500, 600]

In [12]:

numpy_array_1 * 10

Out[12]:

array([400, 500, 600])

Среднее значение вектора

In [13]:

def vector_mean(vecs):
    n = len(vecs)
    return scalar_multiply(1/float(n), sum_of_all_vectors(vecs))

In [14]:

vector_mean([python_list_1, python_list_2, python_list_3])

Out[14]:

[28.333333333333332, 25.0, 43.33333333333333]

In [15]:

np.mean([numpy_array_1, numpy_array_2, numpy_array_3], axis=0)

Out[15]:

array([ 28.33333333,  25.        ,  43.33333333])

Скалярное произведение

In [16]:

def dot_product(v, w):
    """v1 * w1 + .. + vn * wn"""
    return sum(vi * wi for vi, wi in zip(v, w))

In [17]:

dot_product(python_list_1, python_list_2)

Out[17]:

3200

In [18]:

np.sum(numpy_array_1 * numpy_array_2)

Out[18]:

3200

Сумма квадратов

In [19]:

def sum_of_squares(v):
    """ v1 * v1 + v2 * v2 ... + vn * vn"""
    # или return dot_product(v, v)
    return sum(vi ** 2 for vi in v)

In [20]:

sum_of_squares(python_list_1)

Out[20]:

7700

In [21]:

np.sum(numpy_array_1 ** 2)

Out[21]:

7700

Величина вектора

In [22]:

import math

def magnitude(v):
    return math.sqrt(sum_of_squares(v))

In [23]:

magnitude(python_list_1)

Out[23]:

87.74964387392122

In [24]:

np.linalg.norm(numpy_array_1)

Out[24]:

87.749643873921215

Расстояние между двумя векторами

In [25]:

# sqrt((v1 - w1) **2 + ... + (vn - wn) ** 2)

In [26]:

def distance(v, w):
    return magnitude(subtract_vectors(v, w))

In [27]:

distance(python_list_1, python_list_2)

Out[27]:

51.96152422706632

In [28]:

np.linalg.norm(numpy_array_1 - numpy_array_2)

Out[28]:

51.96152422706632

На заметку

В ряде рассмотренных примеров используется sum(). Чем отличается встроенная Python-функция sum() от numpy.sum()? Например тем, что numpy.sum() быстрее обрабатывает numpy-массивы, но медленнее Python-списки.

Проверим в Python 2.7.2 и Numpy 1.6.1:

import numpy as np
import timeit

x = range(1000)
# or 
#x = np.random.standard_normal(1000)

def pure_sum():
    return sum(x)

def numpy_sum():
    return np.sum(x)

n = 10000

t1 = timeit.timeit(pure_sum, number = n)
print 'Pure Python Sum:', t1
t2 = timeit.timeit(numpy_sum, number = n)
print 'Numpy Sum:', t2

Результат при x = range(1000):

Pure Python Sum: 0.445913167735
Numpy Sum: 8.54926219673

Результат при x = np.random.standard_normal(1000):

Pure Python Sum: 12.1442425643
Numpy Sum: 0.303303771848

Согласитесь, имеет смысл учитывать контекст использования.


В основе статьи — материал Бена Алекса Кина. Мой небольшой вклад — перевод, идиоматический код numpy-примеров и дополнительные пояснения.