pyth_08_array
Заметка 8. Вычисления над векторами, графика и линейная регрессия.
курса Математический практикум по Питону.
Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф
Версия 0.11

Анотация

Вводятся базовые элементы питона (Python версии 3.xx) на базе ключевых библиотек относящихся к анализу данных: numpy и matplotlib.

Это предварительная версия! Любые замечания приветсвуются.

Вычисления над векторами, матрицами

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

In [1]:
import numpy as np
In [2]:
# Один из способов, как вычислить значение от каждого элемента списка, было показано выше.
# Показанный способ (отображение) был явный. Он был реализован на чистом питоне без вспомогательных средств.
# Но можно вычислить функцию от каждого элемента списка инче, использую вспомогательные (библиотечные) функции.
a = np.sin( [1.1, 2.2, -4.4] )
a
# Полученый объект уже не будет списоком (см. слово array). Он преврятится в массив, т.е. array.
Out[2]:
array([0.89120736, 0.8084964 , 0.95160207])

Тип array с точки зрения обычных языков фактически является массивом, т.е. объектом у которого тип элементов постоянен. По суте идексируемая переменная (имеет тот же тип). Такой тип соответсвует математическим понятиям вектора, матрице. Переменая с индексом. В следующей заметке перейдем к описанию массивов.

In [3]:
[1.1, 2.2, -4.4] + [2.5, 1.9, 5.1] # Они совместяться в один список (контакенация)
Out[3]:
[1.1, 2.2, -4.4, 2.5, 1.9, 5.1]

Создание

Для содания списоков чисел, которые можно складывать поэлементно, нужны именно вектора/массивы.

In [4]:
# Они из списка создаются так:
np.array( [1.1, 2.2, -4.4])
Out[4]:
array([ 1.1,  2.2, -4.4])

Размер массива всегда можно запросить у самого объекта.

In [5]:
l = np.array( [1.1, 2.2, -4.4]) # Сохраняем массив в переменную l.
l.shape # Запрашиваем у переменной её размер. Он хранится в переменной shape.
Out[5]:
(3,)
In [6]:
bb = [55, 22, 33] # Создаем список.
bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-2f84abcef0cf> in <module>
      1 bb = [55, 22, 33] # Создаем список.
----> 2 bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.

AttributeError: 'list' object has no attribute 'shape'

Замечу, что тип размер/размерность не указывает. Последнее связано с тем, что все элементы массива должны иметь одтн и тот же тип.

In [7]:
type( l ) # Запросим и тип.
Out[7]:
numpy.ndarray
In [8]:
l.dtype # Тип элементов массива.
Out[8]:
dtype('float64')

Функцией

In [9]:
l = range(3, 10) # Создаем список с 3 до 10 (не включительно) с шагом 1.
np.array( l ) # Создаем по списку массив.
Out[9]:
array([3, 4, 5, 6, 7, 8, 9])
In [10]:
np.array(range(1,15.4,2))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-8c2f5b9260d7> in <module>
----> 1 np.array(range(1,15.4,2))

TypeError: 'float' object cannot be interpreted as an integer

Их также можно генерировать готовой функцией.

In [11]:
np.arange(2, 20, 3) # В данному слачае, это последовательность
# с первого числа (2) по последнее (20!!!), но не включая его, с шагом (3).
Out[11]:
array([ 2,  5,  8, 11, 14, 17])
In [12]:
np.arange(2, 20.5, 1)
Out[12]:
array([ 2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12., 13., 14.,
       15., 16., 17., 18., 19., 20.])

Существуют и другие вспомогательные функции для формирования массивов. Например функция равномерного разбиения отрезка.

In [13]:
l=np.linspace(4, 25, 5) # А эта функция создает числа с первого (4) по последнее (включительно) (25) в количестве (5).

Сложные индексы

In [14]:
l
Out[14]:
array([ 4.  ,  9.25, 14.5 , 19.75, 25.  ])
In [15]:
l[2], type(l[2]) # Считываем значение и тип элемента массива.
Out[15]:
(14.5, numpy.float64)
In [16]:
l[400] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-16-5a57b02a8009> in <module>
----> 1 l[400] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.

IndexError: index 400 is out of bounds for axis 0 with size 5
In [17]:
l[-1], l[1:-1:2] # Такая же история и сложными индексами.
Out[17]:
(25.0, array([ 9.25, 19.75]))

Базовые операции

Операции над массивами по-элементны.

In [18]:
# Такие объекты, массивы, можно складывть по элементно.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1])
Out[18]:
array([3.6, 0.3, 0.7])
In [19]:
# Размер массивов должен совпадать. Иначе система выдаст соответствующую ошибку.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1, 7.1])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-a4f199efa0ef> in <module>
      1 # Размер массивов должен совпадать. Иначе система выдаст соответствующую ошибку.
----> 2 np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1, 7.1])

ValueError: operands could not be broadcast together with shapes (3,) (4,) 

При выводе ошибки система указала, что один массив имеет размер 3, а другой -- 4. Даже была указана их одномерность.

Упр. По массиву значений построй массив отношения текущего элемента к последующему (результирующий массив будет на один элемент меньше).

In [20]:
a0 = np.array([-1, 2, 3])
a0
Out[20]:
array([-1,  2,  3])
In [21]:
a1 = np.array([3, 5, -2])
a1
Out[21]:
array([ 3,  5, -2])
In [22]:
a0 * a1 # Поэлементное умножение массивов, а не скалярное.
Out[22]:
array([-3, 10, -6])
In [23]:
# Ну или в явном виде, т.е. без создания дополнительных промежуточных переменных.
np.array([-2, 7]) * np.array([ -2 , 5]) 
Out[23]:
array([ 4, 35])

Тип элементов

In [24]:
# В отличии от списка, все элементы массива должны иметь один и тот же тип. Если объявить так:
q = np.array( [1.0, "aa"] )
q
Out[24]:
array(['1.0', 'aa'], dtype='<U32')
In [25]:
type(q[0]), type(q[1]) # то число тоже станет строчкой.
Out[25]:
(numpy.str_, numpy.str_)
In [26]:
np.array( [1.0, "aa"] ) + np.array( [2.0, "bb"] ) # Сложить их как раньше не получится.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-635087e26002> in <module>
----> 1 np.array( [1.0, "aa"] ) + np.array( [2.0, "bb"] ) # Сложить их как раньше не получится.

TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U32') dtype('<U32') dtype('<U32')

Вычисления над массивами

В библиотеке есть набор функций для обработки массивов. Так, помимо поэлеметных функий наподобии np.sin

In [27]:
l = np.array(l)
l
Out[27]:
array([ 4.  ,  9.25, 14.5 , 19.75, 25.  ])
In [28]:
3*l
Out[28]:
array([12.  , 27.75, 43.5 , 59.25, 75.  ])
In [29]:
np.sin( l )
Out[29]:
array([-0.7568025 ,  0.17388949,  0.93489506,  0.78360288, -0.13235175])

имеются и редуцирующие к числу. Например,

In [30]:
np.sum( l ) # Сумма элементов
Out[30]:
72.5
In [31]:
np.mean( l ), np.std( l ) # Срежнее значение и среднеквадратичное откланение.
Out[31]:
(14.5, 7.424621202458749)
In [32]:
np.median( l ), np.max( l ), np.min( l ) # Медиана, максимальное и мимимальное значение.
Out[32]:
(14.5, 25.0, 4.0)

Упр. По массиву посчитай скользящее среднее, т.е. среднее окна из например 10 элементов. Окно скользит по массиву.

In [33]:
import matplotlib.pyplot as plt
In [34]:
# Сгенерируем именно равномерный на набор точек.
x = np.linspace(-5, 9.5, 29)
y = x*x - 2*x + 3
plt.plot( x, y, 'g.-')
Out[34]:
[<matplotlib.lines.Line2D at 0x7fbbd93ddc50>]
In [35]:
plt.plot( x, np.sin(x), 'g.-')
plt.plot(x, x*x/100, 'b*--')
plt.xlabel( "Ось абсцисс" )
plt.ylabel( "Ось ординат")
#plt.axis('equal') # Чтобы оси были одного масштаба.
plt.legend( ['sin', 'x^2'] )
Out[35]:
<matplotlib.legend.Legend at 0x7fbbd9302f98>

Произведение векторов

In [36]:
# Скаляроне произведение.
a = np.array([ 1, 2, 3])
b = np.array([ -1, 0, 2])
np.inner( a, b) # Скяларное произведение
Out[36]:
5
In [37]:
a * b # Поэлементное произведение векторов.
Out[37]:
array([-1,  0,  6])
In [38]:
np.sum( a * b ) # Но можно и так.
Out[38]:
5
In [39]:
np.dot(a, b)
Out[39]:
5
In [40]:
# Сопряженное скалярное произведение.
a = np.array([ 1 + 2j, 3 + 4j])
b = np.array([ 1 - 2j, 3 + 4j])
np.vdot(a, b)
Out[40]:
(22-4j)
In [41]:
c = a * b
c
Out[41]:
array([ 5. +0.j, -7.+24.j])
In [42]:
np.dot( a.conjugate(), b )
Out[42]:
(22-4j)

Многомерные массивы

In [43]:
# Расширение данной идеи на двумерные массивы.
a0 = np.array([[-1], [2]]) # На вторую размерность указывают вложенные скобки.
a0 # Отмечу, если раньше a0 был массивом, то теперь стал матрицей, т.е. поменялась размерность.
Out[43]:
array([[-1],
       [ 2]])
In [44]:
b0 = np.array([ [3] , [5]])
b0
Out[44]:
array([[3],
       [5]])
In [45]:
# Размеры у матрицы a0 были:
a0.shape # т.е. две строки, в каждой из которых по одному элементу.
Out[45]:
(2, 1)
In [46]:
a0 = np.array([[-1, 2]]) # Можно сделать матрицу наоборот (транспонирование). 
a0
Out[46]:
array([[-1,  2]])
In [47]:
b0.shape, a0.shape
Out[47]:
((2, 1), (1, 2))
In [48]:
np.dot(a0,b0)
Out[48]:
array([[7]])
In [49]:
a0.shape # Одна строка, содержащая два элемента.
Out[49]:
(1, 2)
In [50]:
# Матрицы можно формировать из других матриц.
a0 = np.array( [ 3, -6 ] )
a1 = np.array( [ -1, 2 ] )
a = np.array( [ a0, a1] )
a
Out[50]:
array([[ 3, -6],
       [-1,  2]])
In [51]:
# Размеры должны конечно должны соответствовать.
a0 = np.array( [ 3, -6, 5 ] )
a1 = np.array( [ -1, 2 ] )
aa = np.array( [ a0, a1] )
aa # Иначе это бует массив объектов. Аккуратно сравни и увидь разницу!
Out[51]:
array([array([ 3, -6,  5]), array([-1,  2])], dtype=object)
In [52]:
np.zeros((4,6))
Out[52]:
array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]])
In [53]:
# Поэлементное умножение матриц (элемент массива является одноэлементным массивом). 
a0 * b0 # Не путать с матричным умножением!
Out[53]:
array([[  9, -18,  15],
       [ 15, -30,  25]])
In [54]:
a0
Out[54]:
array([ 3, -6,  5])
In [55]:
np.array([[[1],[2]],[[3],[4]]])*a0
Out[55]:
array([[[  3,  -6,   5],
        [  6, -12,  10]],

       [[  9, -18,  15],
        [ 12, -24,  20]]])
In [56]:
a0.shape,b0.shape
Out[56]:
((3,), (2, 1))
In [57]:
#b0=np.array([1,2,3])
#a0*b0
In [58]:
# Такие матрицы тоже можно умножать по-элементно.
np.array([[-1, 2]]) * np.array([ [3 , 5]])
Out[58]:
array([[-3, 10]])

Как было показано выше, массивы можно создавать по спискам, т.е. сначала делается список, а потом массив по нему.

In [59]:
v = np.array([-2,1])
v
Out[59]:
array([-2,  1])
In [60]:
a.dot(v) # Обычное произведение вектора на матрицу.
Out[60]:
array([-12,   4])
In [61]:
np.dot( a[0], v), np.dot( a[1], v)
Out[61]:
(-12, 4)

Линейные уравнения

In [62]:
# Зададим матрицу в переменной a.
a = np.array( [ [2, -1], [1, 3] ] ) # 2x  -y = 9
b = np.array( [ 9, 8] ) #             1x +3y = 8
a, b # Вектор значений в b.
Out[62]:
(array([[ 2, -1],
        [ 1,  3]]), array([9, 8]))
In [63]:
# Решаем систему уравнений.
x = np.linalg.solve(a, b)
x
Out[63]:
array([5., 1.])
In [64]:
np.dot(a, x) # Проверяем правильность решения.
Out[64]:
array([9., 8.])
In [65]:
# Проверяем на равенство с учетом машинного эпсилон,
np.allclose(np.dot(a, x), b) # т.е. числа могуть чуть отличаться.
Out[65]:
True
In [66]:
# Например, должны были получить 0,
5 - np.sqrt( 5 ) ** 2 # но жизнь иначе распорядилась.
Out[66]:
-8.881784197001252e-16
In [67]:
a.shape, x.shape
Out[67]:
((2, 2), (2,))
In [68]:
np.dot(a, x.reshape(2,1) )
Out[68]:
array([[9.],
       [8.]])
In [69]:
np.allclose( 5 , np.sqrt( 5 ) ** 2 )
Out[69]:
True
In [70]:
# Проверим, что все-таки когда нет равенства,
np.allclose( 5 - 0.1 , np.sqrt( 5 ) ** 2 ) # будет лож.
Out[70]:
False
In [71]:
np.allclose( 5 , (5**(0.5)) ** 2 )
Out[71]:
True
In [72]:
# Можно вычислить и обратную матрицу в явном виде
# a = np.array( [ [2, -1], [1, 3] ] )
ainv = np.linalg.inv( a )
ainv
Out[72]:
array([[ 0.42857143,  0.14285714],
       [-0.14285714,  0.28571429]])

Решаем систему. Для этого используем произведение вектора на матрицу.

In [73]:
# Умножаем обратную матрицу на вектор значений.
ainv.dot( b ) # Получили тот же ответ, что и раньше.
Out[73]:
array([5., 1.])
In [74]:
# Можно умножить и на матрицу. Тогда произведение матрицы на её обратное должно дать единичную.
np.allclose( np.dot(a, ainv), np.eye(2) )
Out[74]:
True

Линейная регрессия

In [75]:
x = np.linspace(-5, 9.5, 9)
y = 0.5*x - 3
plt.plot(x, y)
plt.axis('equal')
Out[75]:
(-5.725, 10.225, -5.8625, 2.1125)
In [76]:
np.random.randn() # Гауссова случайная величина. Нормальное распределение.
Out[76]:
-1.0644774624508646
In [77]:
# Добавим шума к данным.
yy = y + np.random.randn()
In [78]:
plt.plot(x, yy) # Нарисуем график.
plt.axis('equal') # Оси будут иметь один масштаб.
# Чего-то не видна шума.
Out[78]:
(-5.725, 10.225, -6.021506695708831, 1.9534933042911697)
In [79]:
yy[3]-y[3], yy[1]-y[1]
# Видно, что ко всем зачениям y была добавлена одна и та же случайная величина.
Out[79]:
(-0.1590066957088303, -0.15900669570883075)
In [80]:
# Теперь создадим массив из случайных величин.
yy = y + np.random.randn( y.shape[0] ) # В скобках размер.
In [81]:
plt.plot(x, yy)
plt.axis('equal')
Out[81]:
(-5.725, 10.225, -5.221036556134772, 1.593973758343926)
In [82]:
yy = y + np.random.randn( y.shape[0] )/3 # По-меньше шум.
plt.plot(x, yy, '-b')
plt.plot(x, yy, '*r')
plt.axis('equal')
Out[82]:
(-5.725, 10.225, -5.5760935377645495, 2.0495738116201188)
In [83]:
# Создаем общую матрицу. Присваеваем её переменной A.
A = np.array( [np.ones_like(x), x ] )
A
Out[83]:
array([[ 1.    ,  1.    ,  1.    ,  1.    ,  1.    ,  1.    ,  1.    ,
         1.    ,  1.    ],
       [-5.    , -3.1875, -1.375 ,  0.4375,  2.25  ,  4.0625,  5.875 ,
         7.6875,  9.5   ]])
In [84]:
A = A.T # Транспонирование.
A
Out[84]:
array([[ 1.    , -5.    ],
       [ 1.    , -3.1875],
       [ 1.    , -1.375 ],
       [ 1.    ,  0.4375],
       [ 1.    ,  2.25  ],
       [ 1.    ,  4.0625],
       [ 1.    ,  5.875 ],
       [ 1.    ,  7.6875],
       [ 1.    ,  9.5   ]])
In [85]:
# Матрица прямоугольная.
A.shape # Значит обратной не существует.
Out[85]:
(9, 2)
In [86]:
# Вычисляем псевдо обратную матрицу от A. Метод решения обычных систем не годится.
AA = np.linalg.pinv(A)
AA.shape
Out[86]:
(2, 9)
In [87]:
AA.dot(yy) # Домножаем на значения
# и получаем коэффициенты исходного уравнения.
Out[87]:
array([-2.98378935,  0.47714908])

Упр. Нарисуй график данной прямой. Лучше поверх исходных данных.

In [ ]: