Итак, PyTorch у нас с вами был
успешно установлен и теперь может быть использован для формирования и обучения НС.
Однако, прежде чем переходить непосредственной к построению НС, необходимо
изучить основы работы с тензорами, которые лежат в основе работы пакета PyTorch. Поэтому
несколько следующих занятий мы посвятим краткому обзору тензоров и операций с
ними. Те, кто из вас знаком с пакетом NumPy, увидят большое
сходство многомерных массивов с тензорами. Ссылка на курс NumPy:
https://rutube.ru/plst/535156/
В целом, тензоры
PyTorch можно
воспринимать, как многомерные матрицы. Например:
Тензор: (3, 2, 6)
Тензор: (2,
5) Тензор: (1, 8) или Тензор: (8)
При этом все
элементы одного тензора имеют единый тип данных. Давайте определим тензор
размерностью (3, 5, 2). В PyTorch сделать это можно с помощью
команды:
t = torch.Tensor(3, 5, 2)
Но в последних
версиях рекомендуется использовать функцию empty:
В результате
переменная t будет ссылаться
на тензор размерностью (3, 5, 2), состоящий из случайных вещественных значений
с типом данных float32, то есть, на каждый элемент отводится
32 бита или 4 байта. Убедиться в этом можно, выполнив команду:
Если же
требуется создать тензор с другим типом данных, то дополнительно указывается
параметр dtype, например:
d = torch.empty(3, 5, 2, dtype=torch.int32)
Основные типы
данных, следующие:
Тип данных
|
Описание
|
torch.float16
|
16
бит, с плавающей точкой
|
torch.float32
|
32
бита, с плавающей точкой
|
torch.float64
|
64
бита, с плавающей точкой
|
torch.int16
|
16
бит, целочисленный, знаковый
|
torch.int32
|
32
бита, целочисленный, знаковый
|
torch.int64
|
64
бита, целочисленный, знаковый
|
torch.int8
|
8
бит, целочисленный, знаковый
|
torch.uint8
|
8
бит, целочисленный, беззнаковый
|
torch.bool
|
булевый
(True/False)
|
Если же нам
нужно создать тензор с определенными наборами данных, то рекомендуется
применять функцию tensor следующим образом:
torch.tensor([1]) # одно целочисленное значение 1
torch.tensor([1, 2.0]) # два вещественных значения 1.0 и 2.0
torch.tensor([[1, 2], [3, 4], [5, 6]]) # тензор 3x2 с целочисленным типом
torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float32) # с вещественным типом
Однако если
указать неверные размеры данных, например:
torch.tensor([[1, 2, 3], [1, 2]]) # ошибка
то возникнет
ошибка из-за разных длин строк. В тензорах такое недопустимо, они должны
образовывать таблицы с равным числом элементов по каждой из осей.
Следует
отметить, что в более ранних версиях PyTorch тензоры
различных типов создавались с помощью классов:
Тензор
|
Описание
|
torch.HalfTensor
|
16
бит, с плавающей точкой
|
torch.FloatTensor
|
32
бита, с плавающей точкой
|
torch.DoubleTensor
|
64
бита, с плавающей точкой
|
torch.ShortTensor
|
16
бит, целочисленный, знаковый
|
torch.IntTensor
|
32
бита, целочисленный, знаковый
|
torch.LongTensor
|
64
бита, целочисленный, знаковый
|
torch.CharTensor
|
8
бит, целочисленный, знаковый
|
torch.ByteTensor
|
8
бит, целочисленный, беззнаковый
|
torch.BoolTensor
|
булевый
(True/False)
|
Использовать эти
классы можно следующим образом:
torch.IntTensor([1]) # тензор с одним значением
torch.ByteTensor([[1, 2], [3, 4], [5, 6]]) # две оси, размер 3x2
torch.DoubleTensor(3, 2, 3, 5) # четыре оси, размер 3x2x3x5
Однако согласно
документации эти классы остались для обратной совместимости и предпочтение
следует отдавать функции tensor.
Атрибуты и методы, возвращающие свойства тензора
Давайте для
примера сформируем тензор из вещественных значений следующим образом:
d = [[[1, 2, 3], [4, 5, 6]]]
t = torch.tensor(d, dtype=torch.float32)
Если для него
вызвать метод type:
t.type() # 'torch.FloatTensor'
то получим
класс, на основе которого был создан этот тензор. А для определения типа данных
элементов тензора следует обратиться к свойству dtype:
Есть еще два
полезных метода, возвращающие свойства тензора:
t.dim() # 3 - число осей тензора t
t.size() # torch.Size([1, 2, 3]) - число элементов по каждой из осей
Или же можно
использовать атрибут shape:
t.shape # torch.Size([1, 2, 3])
Конвертация тензоров в массивы NumPy и обратно
Те, кто давно
знаком с языком Python и его основными пакетами, наверняка
знают и использовали очень распространенную библиотеку NumPy для
векторно-матричных операций. Кроме того, эта библиотека используется во многих
других пакетах, например, Pandas, Matplotlib и многих
других. В результате, на практике, нередко случается, что заготовленные данные
хранятся в массивах NumPy и их необходимо перенести в тензоры
фреймворка PyTorch. Либо,
наоборот, из тензоров снова получить массивы NumPy. Конечно же,
разработчики пакета PyTorch предусмотрели такую возможность.
Например,
чтобы массив NumPy:
import numpy as np
d_np = np.array([[1, 2, 3], [4, 5, 6]])
преобразовать в
тензор PyTorch, можно
воспользоваться специально предназначенной для этого функцией from_numpy:
t2 = torch.from_numpy(d_np)
либо с помощью
команды:
t3 = torch.tensor(d_np, dtype=torch.float32)
Отличие между
двумя этими подходами заключается в том, что функция from_numpy формирует
тензор с теми же самыми характеристиками (размерность, тип данных), что и
массив d_np. А функция tensor дополнительно
позволяет указать требуемый тип данных. И здесь есть один важный нюанс. Функция
from_numpy не копирует данные из массива d_np в тензор t2, а использует
одно и то же хранилище – массив d_np. В этом легко
убедиться, если изменить значение какого-либо элемента в массиве d_np, например:
Тогда и тензор t2 также изменит
свое содержимое:
tensor([[5,
2, 3],
[4, 5,
6]], dtype=torch.int32)
А вот на тензор t3 это никак не
повлияет. Даже при совпадающих типах данных:
t3 = torch.tensor(d_np, dtype=torch.int32)
выполняется копирование,
и изменение элемента:
не сказывается на
изменении элементов массива d_np. Однако это не
срабатывает при явном использовании классов тензоров. Например, команда:
tt = torch.IntTensor(d_np)
не создает копию
и изменение элемента:
приведет к
изменению и в массиве d_np. Это следует
иметь в виду.
Обратите
внимание, что все вышесказанное не касается списков. Если создать тензор
командами:
d = [[1, 2, 3], [4, 5, 6]]
t = torch.IntTensor(d)
tt = torch.tensor(d)
то список d и тензоры t, tt будут
независимы между собой, так как формат хранения данных в списках и тензорах
отличается.
Наконец, если
нам нужно обратно преобразовать тензор в массив NumPy, то
используется метод numpy. Например:
И здесь массив d и тензор t2 будут связаны
между собой. Поэтому для создания копии можно использовать дополнительно метод copy:
или явно создать
еще один массив NumPy:
Но первый
вариант нагляднее и проще в записи.
Методы преобразования типов данных
Бывают ситуации,
когда требуется изменить тип данных в том или ином тензоре. Например, тензор t2 с типом данных
torch.int32 мы бы хотели заменить на тип torch.float32. Для этого
можно воспользоваться методом float:
будет возвращен
новый тензор tf с измененным
типом данных. При этом сам тензор t2 не меняется. Если же требуется
поменять тип данных в текущем тензоре, то следует выполнить команду:
И так для всех остальных
типов данных:
Метод преобразования типа
|
Тип данных
|
half()
|
torch.float16
|
float()
|
torch.float32
|
double()
|
torch.float64
|
short()
|
torch.int16
|
int()
|
torch.int32
|
long()
|
torch.int64
|
char()
|
torch.int8
|
byte()
|
torch.uint8
|
bool()
|
torch.bool
|
Разумеется, если
тензор приводится к своему собственному типу, то данные не копируются, а просто
возвращается ссылка на этот же тензор:
t = torch.tensor([1, 2, 3])
t2 = t.long() # возвращается тот же самый тензор
На этом мы
завершим первое знакомство с тензорами фреймворка PyTorch. На следующем
продолжим и рассмотрим методы автозаполнения и преобразования формы.