Продолжаем
знакомство с тензорами фреймворка 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 будут содержать
число -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.
Например:
получим
результат (как пример):
tensor([[4.,
1., 5., 2.],
[4., 3.,
4., 4.]])
Обратите
внимание, что значения дискретны в диапазоне [1; 7). Для генерации непрерывных
значений можно воспользоваться другим методом:
получим
результат (как пример):
tensor([[3.9419, 4.4382, 1.7232, 1.8711],
[5.6320,
3.2965, 5.4654, 4.1710]])
Методы преобразования формы тензоров
В PyTorch один и тот же
тензор может иметь множество различных представлений и, как следствие,
математически обрабатываться по-разному.
О чем здесь идет
речь? Смотрите. Изначально тензор:
имеет одну
размерность (ось), содержащую 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 состоит из трех
строк и девяти столбцов:
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:
Но на практике
чаще записывают метод view.
Другой inplace метод resize_ позволяет не
только изменить форму тензора, но и его общее количество элементов. Например:
В результате
тензор x будет содержать
две строки и три столбца:
tensor([[0,
1, 2],
[ 3,
4, 5]])
При этом он
по-прежнему ссылается на те же данные. То есть, изменение значения в тензоре x:
приведет к
соответствующим изменениям всех связанных с этим хранилищем в других тензорах (d и r).
Если нам
требуется многомерный тензор преобразовать в обычный одномерный, то можно
воспользоваться методом ravel(), который возвращает новое
представление, не меняя текущего:
t = x.ravel() # с ссылается на одномерное представление тензора x
Следующий весьма
полезный метод:
d = d.permute(1, 0) # оси 0 и 1 меняются местами
позволяет
поменять указанные оси местами. В данном примере выполняется транспонирование
двумерного тензора 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 для того или иного тензора.