Шаг 41.
Глубокое обучение на Python. Математические основы нейронных сетей. Шестеренки нейронных сетей: операции с тензорами. Поэлементные операции

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

    Операции relu и сложение - это поэлементные операции, то есть такие, которые применяются к каждому отдельному элементу в тензоре. Они поддаются массовому распараллеливанию (векторизации - термин пришел из архитектуры векторного процессора суперкомпьютера периода 1970-1990). Для реализации поэлементных операций на Python можно использовать цикл for, как в следующем примере реализации операции relu:

def naive_relu(x):
    # Убедиться что x — двумерный тензор NumPy
    assert len(x.shape) == 2

    x = x.copy()  # Исключить перезапись исходного тензора
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i, j] = max(x[i, j], 0)
    return x

    Точно так же реализуется сложение:

def naive_add(x, y):
    # Убедиться что x, y — двумерные тензоры NumPy
    assert len(x.shape) == 2
    assert x.shape == y.shape

    x = x.copy()  # Исключить перезапись исходного тензора
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i, j] += y[i, j]
    return x

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

    При работе с массивами NumPy можно пользоваться уже готовыми, оптимизированными реализациями этих операций, доступными в виде функций из пакета NumPy, которые сами делегируют основную работу реализациям базовых подпрограмм линейной алгебры (Basic Linear Algebra Subprograms, BLAS), если они установлены (конечно же, они должны быть установлены). BLAS - это комплект низкоуровневых, параллельных и эффективных процедур для вычислений с тензорами, которые обычно реализуются на Fortran или C.

    Иными словами, при использовании NumPy поэлементные операции можно записывать, как показано ниже, и выполняться они будут почти мгновенно:

import numpy as np
z = x + y # Поэлементное сложение
z = np.maximum(z, 0.) # Поэлементная операция relu

    Давайте измерим, насколько этот процесс "мгновенный":

import time
x = np.random.random((20, 100))
y = np.random.random((20, 100))
t0 = time.time()
for _ in range(1000):
    z = x + y
    z = np.maximum(z, 0.)
print("Took: {0:.2f} s".format(time.time() - t0))

    У нас эта операция выполнилась за 0,01 секунды, тогда как предыдущей наивной реализации потребовалось 3,62 секунды:

t0 = time.time()
for _ in range(1000):
    z = naive_add(x, y)
    z = naive_relu(z)
print("Took: {0:.2f} s".format(time.time() - t0))

    Аналогично при запуске на графическом процессоре код TensorFlow выполняет поэлементные операции с применением полностью векторизованных реализаций CUDA, которые максимально эффективно используют архитектуру графического процессора с высокой степенью параллелизма.

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




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