Создание тензоров. Конвертирование в NumPy

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

Итак, 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 = torch.empty(3, 5, 2)

В результате переменная t будет ссылаться на тензор размерностью (3, 5, 2), состоящий из случайных вещественных значений с типом данных float32, то есть, на каждый элемент отводится 32 бита или 4 байта. Убедиться в этом можно, выполнив команду:

t.dtype

Если же требуется создать тензор с другим типом данных, то дополнительно указывается параметр 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.dtype # torch.float32

Есть еще два полезных метода, возвращающие свойства тензора:

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, например:

d_np[0, 0] = 5

Тогда и тензор t2 также изменит свое содержимое:

tensor([[5, 2, 3],
        [4, 5, 6]], dtype=torch.int32)

А вот на тензор t3 это никак не повлияет. Даже при совпадающих типах данных:

t3 = torch.tensor(d_np, dtype=torch.int32)

выполняется копирование, и изменение элемента:

t3[0, 0] = 1

не сказывается на изменении элементов массива d_np. Однако это не срабатывает при явном использовании классов тензоров. Например, команда:

tt = torch.IntTensor(d_np)

не создает копию и изменение элемента:

tt[0, 0] = 1

приведет к изменению и в массиве d_np. Это следует иметь в виду.

Обратите внимание, что все вышесказанное не касается списков. Если создать тензор командами:

d = [[1, 2, 3], [4, 5, 6]]
t = torch.IntTensor(d)
tt = torch.tensor(d)

то список d и тензоры t, tt будут независимы между собой, так как формат хранения данных в списках и тензорах отличается.

Наконец, если нам нужно обратно преобразовать тензор в массив NumPy, то используется метод numpy. Например:

d = t2.numpy()

И  здесь массив d и тензор t2 будут связаны между собой. Поэтому для создания копии можно использовать дополнительно метод copy:

d = t2.numpy().copy()

или явно создать еще один массив NumPy:

d = np.array(t2.numpy())

Но первый вариант нагляднее и проще в записи.

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

Бывают ситуации, когда требуется изменить тип данных в том или ином тензоре. Например, тензор t2 с типом данных torch.int32 мы бы хотели заменить на тип torch.float32. Для этого можно воспользоваться методом float:

tf = t2.float()

будет возвращен новый тензор tf с измененным типом данных. При этом сам тензор t2 не меняется. Если же требуется поменять тип данных в текущем тензоре, то следует выполнить команду:

t2 = t2.float()

И так для всех остальных типов данных:

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

Тип данных

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. На следующем продолжим и рассмотрим методы автозаполнения и преобразования формы.

Видео по теме