Шаг 43.
Глубокое обучение на Python. Математические основы нейронных сетей. ... операции с тензорами. Скалярное произведение тензоров

    На этом шаге мы рассмотрим особенности выполнения этой операции.

    Скалярное произведение, также иногда называемое тензорным произведением (не путайте с поэлементным произведением, оператором *), - наиболее общая и наиболее полезная операция с тензорами.

    Поэлементное произведение в NumPy выполняется с помощью функции np.dot:

x = np.random.random((32,))
y = np.random.random((32,))
z = np.dot(x, y)

    В математике скалярное произведение обозначается точкой (·):

z = x · y

    Что же делает операция скалярного произведения? Для начала разберемся со скалярным произведением двух векторов, x и у:

def naive_vector_dot(x, y):
    # Убедиться что x, y — векторы NumPy
    assert len(x.shape) == 1
    assert len(y.shape) == 1
    assert x.shape[0] == y.shape[0]

    z = 0.
    for i in range(x.shape[0]):
        z += x[i] * y[i]
    return z

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

    Также есть возможность получить скалярное произведение матрицы x на вектор y, являющееся вектором, элементы которого - скалярные произведения строк x на y. Вот как реализуется эта операция:

import numpy as np


def naive_matrix_vector_dot(x, y):
    assert len(x.shape) == 2  # Убедиться что x — матрица NumPy
    assert len(y.shape) == 1  # Убедиться что y — вектор NumPy
    # Первое измерение x должно совпадать с нулевым измерением y!
    assert x.shape[1] == y.shape[0]

    # Эта операция вернет вектор с нулевыми элементами, имеющий ту же форму, что и y
    z = np.zeros(x.shape[0])
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            z[i] += x[i, j] * y[j]
    return z

    Эта операция вернет вектор с нулевыми элементами, имеющий ту же форму, что и y.

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

def naive_matrix_vector_dot(x, y):
    z = np.zeros(x.shape[0])
    for i in range(x.shape[0]):
        z[i] = naive_vector_dot(x[i, :], y)
    return z

    Обратите внимание, что если один из двух тензоров имеет ndim больше 1, скалярное произведение перестает быть симметричной операцией, то есть результат dot(x, y) не совпадает с результатом dot(y, x).

    Разумеется, скалярное произведение можно распространить на тензоры с произвольным количеством осей. Наиболее часто на практике применяется скалярное произведение двух матриц. Получить скалярное произведение двух матриц x и y (dot(x, y)) можно, только если x.shape[1] == y.shape[0]. В результате получится матрица с формой (x.shape[0], y.shape[1]), элементами которой являются скалярные произведения строк x на столбцы y. Вот как могла бы выглядеть простейшая реализация:

def naive_matrix_dot(x, y):
    assert len(x.shape) == 2  # Убедиться что x, y — матрицы NumPy
    assert len(y.shape) == 2
    # Первое измерение x должно совпадать с нулевым измерением y!
    assert x.shape[1] == y.shape[0]

    # Эта операция вернет матрицу заданной формы с нулевыми элементами
    z = np.zeros((x.shape[0], y.shape[1]))
    for i in range(x.shape[0]):      # Обход строк в x...
        for j in range(y.shape[1]):  # ...и столбцов в y
            row_x = x[i, :]
            column_y = y[:, j]
            z[i, j] = naive_vector_dot(row_x, column_y)
    return z

    Эта операция вернет матрицу заданной формы с нулевыми элементами.

    Чтобы было понятнее, как определяется совместимость форм матриц для скалярного произведения, представьте входные и выходной тензоры, как показано на рисунке 1.


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

    Переменные x, y и z изображены здесь в виде прямоугольников (буквально - таблиц элементов). Число строк в x и столбцов в y должно совпадать, соответственно, ширина x должна равняться высоте y.

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

(a, b, c, d) · (d,) → (a, b, c)
(a, b, c, d) · (d, e) → (a, b, c, e)
и так далее.

    На следующем шаге мы рассмотрим изменение формы тензора.




Предыдущий шаг Содержание Следующий шаг