Тензоры. Автозаполнение, изменение формы

Смотреть материал на YouTube | RuTube

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

Начнем с формирования тензоров, состоящие из нулей. Это делается с помощью функции zeros следующим образом:

tz = torch.zeros(2, 3) # тензор 2x3 из нулей и типом torch.float32

Дополнительно можно указать желаемый тип данных тензора:

tz = torch.zeros(2, 3, dtype=torch.int32)

Аналогичная функция ones, формирующая тензор из единиц:

t = torch.ones(2, 3) # тензор 2x3 из единиц и типом torch.float32
t = torch.ones(2, 3, dtype=torch.long)

Следующая функция eye формирует единичную матрицу:

torch.eye(3) # тензор 3x3 с единицами по главной диагонали
torch.eye(3, 2) # тензор 3x2 с единицами по главной диагонали
torch.eye(3, 2, dtype=torch.int8)

Следующая функция full позволяет формировать тензор с заданным числовым значением:

t = torch.full((2, 4), 5) # тензор 2x4 с числами 5

Следующая распространенная функция arange формирует одномерный тензор со значениями арифметической прогрессии:

torch.arange(7) # tensor([0, 1, 2, 3, 4, 5, 6])
torch.arange(-5, 0) # tensor([-5, -4, -3, -2, -1])
torch.arange(-5, 0, 2) # tensor([-5, -3, -1])
torch.arange(1, 0, -0.2) # tensor([1.0000, 0.8000, 0.6000, 0.4000, 0.2000])

Функция linspace позволяет разбить указанный диапазон на равномерные отрезки:

torch.linspace(1, 5, 0) # tensor([])
torch.linspace(1, 5, 1) # tensor([1.])
torch.linspace(1, 5, 2) # tensor([1., 5.])
torch.linspace(1, 7, 4) # tensor([1., 3., 5., 7.])

Следующая группа функций позволяет создавать тензоры со случайными значениями заданных распределений:

torch.rand(2, 3) # тензор 2x3 с равномерным распределением в диапазоне [0; 1)
torch.randn(2, 3) # тензор 2x3 с нормальным распределением с нулевым средним и единичной дисперсией

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

torch.manual_seed(num_seed) # num_seed – любое (как правило, целое) число

inplace методы

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

torch.IntTensor(2, 5).zero_()

Сначала формируется тензор размером 2x5, а затем, он заполняется нулями благодаря вызову метода zero_. Обратите внимание на подчеркивание после его имени. В PyTorch принято соглашение, что методы (и функции) с такими именами изменяют текущий тензор, не создавая новый. Это, так называемые, inplace методы, или еще говорят mutable методы. В противоположность им immutable методы (и функции) не меняют текущий тензор, а создают новый.

Давайте предположим, что сформирован некий тензор:

x = torch.FloatTensor(2, 4)

И после этого потребовалось заполнить его единицами:

x.fill_(1)

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

x.fill_(-0.3)

и все элементы тензора x будут содержать число -0.3.

Или же имеется следующая группа inplace методов для заполнения тензора случайными значениями:

  • x.random_(from, to) # тензор заполняется случайными дискретными значениями в диапазоне [from; to) с равномерным распределением
  • x.uniform_(from=0, to=1) # тензор заполняется случайными непрерывными значениями в диапазоне [from; to) с равномерным распределением
  • x.normal_(mean=0, std=1) # тензор заполняется случайными гауссовскими значениями со средним mean и стандартным отклонением std.

Например:

x.random_(1, 7)

получим результат (как пример):
tensor([[4., 1., 5., 2.],
        [4., 3., 4., 4.]])

Обратите внимание, что значения дискретны в диапазоне [1; 7). Для генерации непрерывных значений можно воспользоваться другим методом:

x.uniform_(1, 7)

получим результат (как пример):

tensor([[3.9419, 4.4382, 1.7232, 1.8711],
        [5.6320, 3.2965, 5.4654, 4.1710]])

Методы преобразования формы тензоров

В PyTorch один и тот же тензор может иметь множество различных представлений и, как следствие, математически обрабатываться по-разному.

О чем здесь идет речь? Смотрите. Изначально тензор:

x = torch.arange(27)

имеет одну размерность (ось), содержащую 27 элементов:

tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26])

Однако можно создать другое его представление (форму), используя метод view. Например:

d = x.view(3, 9)

Теперь тензора d состоит из трех строк и девяти столбцов:

tensor([[ 0,  1,  2,  3,  4,  5,  6,  7,  8],
        [ 9, 10, 11, 12, 13, 14, 15, 16, 17],
        [18, 19, 20, 21, 22, 23, 24, 25, 26]])

При этом тензор d и тензор x ссылаются на одно и то же хранилище данных:

Аналогичного эффекта можно добиться с помощью метода reshape:

r = x.reshape(3, 3, 3)

Но на практике чаще записывают метод view.

Другой inplace метод resize_ позволяет не только изменить форму тензора, но и его общее количество элементов. Например:

x.resize_(2, 3)

В результате тензор x будет содержать две строки и три столбца:

tensor([[0,  1,  2],
        [ 3,  4,  5]])

При этом он по-прежнему ссылается на те же данные. То есть, изменение значения в тензоре x:

x[0, 0] = -1

приведет к соответствующим изменениям всех связанных с этим хранилищем в других тензорах (d и r).

Если нам требуется многомерный тензор преобразовать в обычный одномерный, то можно воспользоваться методом ravel(), который возвращает новое представление, не меняя текущего:

t = x.ravel() # с ссылается на одномерное представление тензора x

Следующий весьма полезный метод:

d = d.permute(1, 0) # оси 0 и 1 меняются местами

позволяет поменять указанные оси местами. В данном примере выполняется транспонирование двумерного тензора d. Или же можно получить транспонированное представление тензора через атрибут mT:

d = d.mT

Добавление и удаление осей

Часто при работе с тензорами PyTorch требуется добавлять новые оси и удалять существующие. Есть множество способов выполнять эти операции, но мы рассмотрим два наиболее распространенных с помощью функций:

  • torch.unsqueeze(x, dim) – добавление новой оси;
  • torch.squeeze(x[, dim]) – удаление оси (без удаления элементов).

Давайте предположим, что имеется некий многомерный тензор:

x_test = torch.arange(32).view(8, 2, 2) # тензор 8x2x2

и нам потребовалось добавить еще одно измерение (ось), причем, в самое начало, то есть, ось axis0. Сейчас на этой оси 8 элементов – матриц 2x2, но мы хотим сделать четырехмерный массив, сохранив остальные три оси и их данные без изменений. Как раз это достаточно просто сделать с помощью функции unsqueeze, следующим образом:

x_test4 = torch.unsqueeze(x_test, dim=0) # torch.Size([1, 8, 2, 2])

Или, то же самое можно выполнить с помощью методов:

r = x_test.unsqueeze(0) # формирование нового представления
x_test.unsqueeze_(0) # изменение текущего тензора

Видим, что тензор стал четырехмерным и первая добавленная ось axis0 содержит один элемент – трехмерный тензор 8x2x2. Причем функции (и методы) возвращают лишь новое представление тех же самых данных, то есть, новые тензоры здесь не создаются.

По аналогии можно добавить последнюю ось в тензор следующим образом:

b = torch.unsqueeze(x_test4, dim=-1) # размерность (1, 8, 2, 2, 1)

или

b = x_test4.unsqueeze(dim=-1)
x_test4.unsqueeze_(dim=-1)

Отрицательный индекс -1 – это следующая с конца ось. Если указать индекс -2, то добавится предпоследняя ось и так далее. Отрицательные индексы очень удобно использовать при работе с тензорами произвольных размерностей.

Следующая функция squeeze позволяет удалить все оси с одним элементом. Например, команда:

c = torch.squeeze(b) # размерность (8, 2, 2)

превращает тензор размерностью (1, 8, 2, 2) в тензор размерностью (8, 2, 2). При необходимости, дополнительно мы можем самостоятельно указать оси, которые следует удалять, например, так:

c = torch.squeeze(b, dim=0) # удаляет только ось axis0, не затронув другие

Но, если указать ось с числом элементов больше 1, то ничего не изменится:

c = torch.squeeze(b, dim=1) # тензор не изменится

По аналогии можно использовать метод squeeze для того или иного тензора.

Видео по теме