Для тензоров доступно более 1200 операций, таких как арифметические операции, линейную алгебру, манипуляции с матрицами (транспонирование, индексирование, нарезку), выборку и т.д.. Эти операции могут выполняться на центральном или графическом процессоре. Рассмотрим некоторые самые базовые операции.
Используя квадратные скобки [], можно обращаться к элементам тензора (подобно тому, как это делается в списках Python):
import torch
tensor = torch.tensor([[1, 2],[3, 4]])
# получаем элементы
print(f"Первая строка: {tensor[0]}") # [1, 2]
print(f"Второй элемент (1-я строка 2-й столбец): {tensor[0][1]}") # 2
# изменяем элементы
tensor[0][1] = 4 # изменяем второй столбец первой строки
tensor[1] = torch.tensor([11, 12]) # изменяем вторую строку
print(tensor)
Консольный вывод:
Первая строка: tensor([1, 2])
Второй элемент (1-я строка 2-й столбец): 2
tensor([[ 1, 4],
[11, 12]])
Для каждой размерности тензора можно использовать свою пару квадратных скобок, либо можно внутри одной пары квадратных скобок передать индексы для каждой размерности. Так, следующие выражения будут идентичны:
tensor[0][1] tensor[0, 1]
Отрицательные значения начального и конечного индексов интерпретируются номер элемента с конца в виде n-i, где n - количество элементов в данной размерности, а i - индекс.
import torch tensor = torch.tensor([[1, 2],[3, 4]]) tensor[0, -2] = 4 # изменяем второй столбец с конца первой строки print(tensor) # tensor([[ 4, 2], [11, 12]])
Для получения диапазона элементов из тензора можно использовать операцию : в виде начало:конец:шаг. Например:
import torch tensor = torch.tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) print(tensor[1:4]) # tensor([1, 2, 3]) print(tensor[1:8:2]) # tensor([1, 3, 5, 7])
В первом случае (tensor[1:4]) получаем диапазон со второго элемента (индекс 1) до 5-го элемента не включая (до элемента с индексом 4)
Во второом случае (tensor[1:8:2]) получаем диапазон со второго элемента (индекс 1) до 9-го элемента не включая (до элемента с индексом 8) с шагом в 2 элемента.
Можно использовать отрицательные индексы, тогда отсчет будет идти с конца:
import torch tensor = torch.tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) print(tensor[-4:-1]) # tensor([6, 7, 8])
Также можно использовать один из индексов, либо не использовать вовсе. Если оба индекса не указаны, то выбирается весь список. Если не указан начальный индекс, то в качестве начала используется самый первый элемент. Если не указан конечный индекс, то в качестве него применяется самый последний элемент:
import torch tensor = torch.tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) print(tensor[:]) # tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) print(tensor[2:]) # tensor([2, 3, 4, 5, 6, 7, 8, 9]) print(tensor[:7]) # tensor([0, 1, 2, 3, 4, 5, 6])
Подобным образом мы можем выбирать диапазаоны и в многомерных тензорах. Например, если у нас двухмерный тензор (матрица), то мы можем использовать диапазон для выбора определенных строк и их определенных столбцов. Например, выберем второй столбец матрицы определенных строк:
import torch
tensor = torch.tensor([
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
])
# выбираем второй столбец
print(tensor[:, 1]) # tensor([1, 4, 7]) - все строк
print(tensor[1:, 1]) # tensor([4, 7]) - 2 и 3 строк
print(tensor[:2, 1]) # tensor([1, 4]) - 1 и 2 строк
Еще один элемент, который можно использовать, - это многоточие .... Многоточие расширяется до количества объектов (:), необходимых для индексации всех измерений. Например:
import torch
tensor = torch.tensor([
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
])
# выбираем первый столбец
print(tensor[..., 0]) # tensor([0, 3, 6])
# выбираем последний столбец
print(tensor[..., -1]) # tensor([2, 5, 8])
Для объединения тензоров по определенному измерению можно использовать функцию torch.cat():
import torch
tensor1 = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
tensor2 = torch.tensor([
[6, 7, 8],
[9, 10, 11]
])
# объединяем по первой размерности
tensor_all = torch.cat([tensor1, tensor2], dim=0)
print("dim=0\n", tensor_all)
# объединяем по второй размерности
tensor_all = torch.cat([tensor1, tensor2], dim=1)
print("dim=1\n", tensor_all)
Первым параметром в функцию torch.cat передается массив объединяемых тензоров, а второй параметр представляет размерности, по которой ижет объединение. Так, в примере выше в первом случае с
dim=0 объединяем по первой размерности (то есть все строки), а во втором случае (dim=1) объединяем столбцы внутри строк. Консольный вывод программы:
dim=0
tensor([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
dim=1
tensor([[ 0, 1, 2, 6, 7, 8],
[ 3, 4, 5, 9, 10, 11]])
В PyTorch арифметические операции над тензорами включают сложение, вычитание, умножение и деление. Эти операции можно выполнять поэлементно между тензорами или между тензорами и скалярами, автоматически применяя операцию к каждому элементу, что упрощает математические вычисления с многомерными данными. Рассмотрим некоторые из этих операций. Операции со скалярным значением:
import torch
tensor1 = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
# сложение со скаляром
tensor2 = tensor1 + 1
print(tensor2)
# Результат:
# tensor([[1, 2, 3],
# [4, 5, 6]])
# вычитание скалярного значения
tensor3 = tensor1 - 1
print(tensor3)
# Результат:
# tensor([[-1, 0, 1],
# [ 2, 3, 4]])
# умножение на скаляр
tensor4 = tensor1 * 2
print(tensor4)
# Результат:
# tensor([[ 0, 2, 4],
# [ 6, 8, 10]])
Аналогичные операции между тензорами:
import torch
tensor1 = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
# создаем единичную матрицу с числами типа int
tensor2 = torch.ones((2, 3), dtype= torch.int)
print(tensor2)
# tensor([[1, 1, 1],
# [1, 1, 1]], dtype=torch.int32)
# сложение тензоров
tensor3 = tensor1 + tensor2
print(tensor3)
# Результат:
# tensor([[1, 2, 3],
# [4, 5, 6]])
# вычитание тензоров
tensor4 = tensor1 - tensor2
print(tensor4)
# Результат:
# tensor([[-1, 0, 1],
# [ 2, 3, 4]])
# умножение тензоров
tensor5 = tensor1 * tensor2
print(tensor5)
# Результат:
# tensor([[0, 1, 2],
# [3, 4, 5]])
Отдельно стоит сказать про умножение. Стандартная операция * представляет поэлементное умножение тензоров. И в качестве альтернативы этой операции можно использовать функцию mul():
import torch
tensor1 = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
tensor2 = torch.tensor([
[1, 2, 1],
[1, 1, 1]
])
# поэлементное умножение тензоров
tensor5 = tensor1 * tensor2
print(tensor5)
# Результат:
# tensor([[0, 2, 2],
# [3, 4, 5]])
tensor6 = tensor1.mul(tensor2)
print(tensor6)
# Результат:
# tensor([[0, 2, 2],
# [3, 4, 5]])
Если же нам надо осуществить умножение матриц, то для этого применяется операция @ или функция matmul():
import torch
tensor1 = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
tensor2 = torch.tensor([
[1, 2],
[1, 2],
[1, 2]
])
# умножение матриц
tensor3 = tensor1.matmul(tensor2)
print(tensor3)
# Результат:
# tensor([[ 3, 6],
# [12, 24]])
tensor4 = tensor1 @ tensor2
print(tensor4)
# Результат:
# tensor([[ 3, 6],
# [12, 24]])
Ряд операций представлены специальными функциями. Например, функция sum() вычисляет сумму всех элементов тензора, а функция dot() - скалярное произведение двух одномерных векторов:
import torch
tensor = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
tensor_sum = tensor.sum()
print(tensor_sum) # tensor(15)
# умножаем вектор [2, 3] на [2, 1]
tensor_dot = torch.dot(torch.tensor([2, 3]), torch.tensor([2, 1]))
print(tensor_dot) # tensor(7)
Причем некоторые операции, как рассмотренные выше sum() и dot(), возвращают тензор с одним элементом. С помощью функции item()
подобный тензор можно преобразовать в скалярное значение языка Python:
import torch
tensor = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
tensor_sum = tensor.sum()
print(tensor_sum) # tensor(15)
value_sum = tensor_sum.item()
print(value_sum) # 15
# умножаем вектор [2, 3] на [2, 1]
tensor_dot = torch.dot(torch.tensor([2, 3]), torch.tensor([2, 1]))
print(tensor_dot) # tensor(7)
print(tensor_dot.item()) # 7
С помощью свойства T можно получить транспонированную матрицу:
import torch
tensor1 = torch.tensor([
[0, 1, 2],
[3, 4, 5]
])
print(tensor1.T)
# tensor([[0, 3],
# [1, 4],
# [2, 5]])
tensor2 = tensor1 @ tensor1.T
print(tensor2)
По умолчанию тензоры создаются на центральном процессоре. Если необходимо задействовать графический процессор для повышения производительности во время обучения модели и вывода, то надо явно переместить тензоры на графический процессор с помощью метода to() после проверки доступности графического процессора. Пример перемещения тензора на граф. процессор:
if torch.accelerator.is_available():
tensor = tensor.to(torch.accelerator.current_accelerator())
Обратите внимание, что перемещение тензора происходит только, если в системе доступен графический процессор с поддержкой CUDA.