Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шолле Ф. - Глубокое обучение на Python (Библиотека программиста) - 2023.pdf
Скачиваний:
6
Добавлен:
07.04.2024
Размер:
11.34 Mб
Скачать

2.3. Шестеренки нейронных сетей: операции с тензорами    69

из.четырех.таких.клипов.потребуется.тензор.с.формой.(4, .240, .144, .256, .3).. То.есть.106.168.320.значений!.Если.предположить,.что.dtype .тензора.определен. как.float32,.тогда.для.хранения.каждого.значения.понадобится.32.бита,.а.для. всего.тензора.соответственно.405.Мбайт..Мощно!.Видеоролики,.с.которыми. вам.придется.столкнуться.в.реальной.жизни,.намного.легковеснее,.потому.что. они.не.хранятся.как.коллекции.значений.типа.float32 .и.обычно.подвергаются. значительному.сжатию.(как,.например,.формат.MPEG).

2.3. ШЕСТЕРЕНКИ НЕЙРОННЫХ СЕТЕЙ: ОПЕРАЦИИ С ТЕНЗОРАМИ

Так.как.любую.компьютерную.программу.можно.свести.к.небольшому.набору. двоичных.операций.с.входными.данными.(И,.ИЛИ,.НЕ.и.др.),.все.преобразования,.выполняемые.глубокими.нейронными.сетями.при.обучении,.можно. свести.к.горстке.операций с тензорами (или тензорных функций),.применяемых. к.тензорам.с.числовыми.данными..Например,.тензоры.можно.складывать,.перемножать.и.т..д.

В.нашем.первом.примере.мы.создали.модель,.наложив.друг.на.друга.два.слоя. Dense..В.библиотеке.Keras.экземпляр.слоя.выглядит.так:

keras.layers.Dense(512, activation='relu')

Этот.слой.можно.интерпретировать.как.функцию,.которая.принимает.матрицу. и.возвращает.другую.матрицу.—.новое.представление.исходного.тензора..В.дан- ном.случае.функция.имеет.следующий.вид.(где.W .—.это.матрица,.а.b .—.вектор;. оба.значения.являются.атрибутами.слоя):

output = relu(dot(input, W) + b)

Давайте.развернем.ее..Здесь.у.нас.имеются.три.операции.с.тензорами:

. скалярное.произведение.(dot).исходного.тензора.input.и.тензора.с.именем.W;

. сложение.(+).получившейся.матрицы.и.вектора.b;

.операция.relu:.relu(x).—.эквивалентна.операции.max(x,.0);.название.relu.про- исходит.от.английского.rectified linear unit.(блок.линейной.корректировки).

ПРИМЕЧАНИЕ

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

Поэтому.мы.будем.везде.применять.код,.использующий.NumPy.и.TensorFlow.

70    Глава 2. Математические основы нейронных сетей

2.3.1. Поэлементные операции

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

def naive_relu(x):

 

Убедиться, что x — двумерный

 

assert len(x.shape) == 2

 

 

 

тензор NumPy

 

 

 

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 — двумерные

 

assert len(x.shape) == 2

 

 

тензоры NumPy

 

 

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

 

 

2.3. Шестеренки нейронных сетей: операции с тензорами    71

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

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,02.секунды,.тогда.как.предыдущей.наивной.реализации.потребовалось.2,45.секунды:

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,.которые.максимально.эффективно.используют.архитектуру.графического.процессора.с.высокой.степенью.параллелизма.

2.3.2. Расширение

Наша.предыдущая.реализация.naive_add .поддерживает.только.сложение.тензоров.второго.ранга.с.идентичными.формами..Но.в.слое.Dense,.представленном. выше,.мы.складывали.двумерный.тензор.с.вектором..Что.происходит.при.сложении,.когда.формы.складываемых.тензоров.отличаются?

Когда.это.возможно.и.не.вызывает.неоднозначности,.меньший.тензор.расширяется.так,.чтобы.его.новая.форма.соответствовала.форме.большего.тензора..

Расширение.выполняется.в.два.этапа.

1.. В.меньший.тензор.добавляются.оси.(так.называемые.оси расширения),.чтобы.значение.его.атрибута.ndim .соответствовало.значению.этого.же.атрибута. большего.тензора.

2.. Меньший.тензор.копируется.в.новые.оси.до.полного.совпадения.с.формой. большего.тензора.

72    Глава 2. Математические основы нейронных сетей

Рассмотрим.конкретный.пример..Допустим,.у.нас.имеются.тензоры.X .с.формой. (32, .10) .и.y .с.формой.(10,):

import numpy as np

 

X — матрица случайных чисел с формой (32, 10)

 

X =

np.random.random((32, 10))

 

 

 

y — вектор случайных чисел с формой (10,)

 

 

y =

np.random.random((10,))

 

 

 

 

Сначала.нужно.добавить.первую.пустую.ось.в.вектор.y,.чтобы.привести.его. форму.к.виду.(1, .10):

y = np.expand_dims(y, axis=0)

 

Теперь y имеет форму (1, 10)

 

Затем.скопируем.y .по.этой.новой.оси.32.раза,.чтобы.в.результате.получился. тензор.Y .с.формой.(32, .10),.где.Y[i, .:] .== .y .для.i .в.диапазоне.range(0, .32).

Y = np.concatenate([y] * 32, axis=0)

 

Скопировать y 32 раза вдоль оси 0,

 

чтобы получить Y с формой (32, 10)

 

 

После.этого.можно.сложить.X .и.Y,.которые.имеют.одинаковую.форму.

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

def naive_add_matrix_and_vector(x, y): assert len(x.shape) == 2. assert len(y.shape) == 1. assert x.shape[1] == y.shape[0]

x = x.copy() for i in range(x.shape[0]):

for j in range(x.shape[1]): x[i, j] += y[j]

return x

Убедиться, что x — двумерный тензор NumPy

Убедиться, что y — вектор NumPy

Исключить перезапись исходного тензора

Прием.расширения.в.общем.случае.можно.использовать.в.поэлементных.операциях. с.двумя.тензорами,.если.один.тензор.имеет.форму.(a,.b,.....n,.n.+.1,.... .m),.а.дру- гой.—.форму.(n, .n .+ .1, .... .m)..В.этом.случае.при.расширении.будут.добавлены. оси.до.n .- .1.

Следующий.пример.показывает.применение.поэлементной.операции.maximum . к.двум.тензорам.с.разными.формами.посредством.расширения:

import numpy as np

 

 

x — тензор случайных чисел,

 

 

x = np.random.random((64, 3, 32, 10))

 

 

имеющий форму (64, 3, 32, 10)

 

 

y = np.random.random((32, 10))

 

y — тензор случайных чисел,

 

z = np.maximum(x, y)

 

 

 

 

 

имеющий форму (32, 10)

 

 

 

 

 

Получившийся тензор z имеет форму (64, 3, 32, 10) аналогично x