pytorch 学习
tensor basics
- Initialization of a Tensor
- Tensor Mathematical Operations and Comparison
- Tensor Indexing
- Tensor Reshaping
import torch
device = "cuda" if torch.cuda.is_available() else "cpu" # Cuda to run on GPU!
1.Initialization of a Tensor
# Initializing a Tensor in this case of shape 2x3 (2 rows, 3 columns)
my_tensor = torch.tensor(
[[1, 2, 3], [4, 5, 6]], dtype=torch.float32, device=device, requires_grad=True
)
# A few tensor attributes
print(
f"Information about tensor: {my_tensor}"
) # Prints data of the tensor, device and grad info
print(
"Type of Tensor {my_tensor.dtype}"
) # Prints dtype of the tensor (torch.float32, etc)
print(
f"Device Tensor is on {my_tensor.device}"
) # Prints cpu/cuda (followed by gpu number)
print(f"Shape of tensor {my_tensor.shape}") # Prints shape, in this case 2x3
print(f"Requires gradient: {my_tensor.requires_grad}") # Prints true/false
# Other common initialization methods (there exists a ton more)
x = torch.empty(size=(3, 3)) # Tensor of shape 3x3 with uninitialized data
x = torch.zeros((3, 3)) # Tensor of shape 3x3 with values of 0
x = torch.rand(
(3, 3)
) # Tensor of shape 3x3 with values from uniform distribution in interval [0,1)
x = torch.ones((3, 3)) # Tensor of shape 3x3 with values of 1
x = torch.eye(5, 5) # Returns Identity Matrix I, (I <-> Eye), matrix of shape 2x3
x = torch.arange(
start=0, end=5, step=1
) # Tensor [0, 1, 2, 3, 4], note, can also do: torch.arange(11)
x = torch.linspace(start=0.1, end=1, steps=10) # x = [0.1, 0.2, ..., 1]
x = torch.empty(size=(1, 5)).normal_(
mean=0, std=1
) # Normally distributed with mean=0, std=1
x = torch.empty(size=(1, 5)).uniform_(
0, 1
) # Values from a uniform distribution low=0, high=1
x = torch.diag(torch.ones(3)) # Diagonal matrix of shape 3x3
# How to make initialized tensors to other types (int, float, double)
# These will work even if you're on CPU or CUDA!
tensor = torch.arange(4) # [0, 1, 2, 3] Initialized as int64 by default
print(f"Converted Boolean: {tensor.bool()}") # Converted to Boolean: 1 if nonzero
print(f"Converted int16 {tensor.short()}") # Converted to int16
print(
f"Converted int64 {tensor.long()}"
) # Converted to int64 (This one is very important, used super often)
print(f"Converted float16 {tensor.half()}") # Converted to float16
print(
f"Converted float32 {tensor.float()}"
) # Converted to float32 (This one is very important, used super often)
print(f"Converted float64 {tensor.double()}") # Converted to float64
# Array to Tensor conversion and vice-versa
import numpy as np
np_array = np.zeros((5, 5))
tensor = torch.from_numpy(np_array)
np_array_again = (
tensor.numpy()
) # np_array_again will be same as np_array (perhaps with numerical round offs)
2.Tensor Math & Comparison Operations
x = torch.tensor([1, 2, 3])
y = torch.tensor([9, 8, 7])
# -- Addition --
z1 = torch.empty(3)
torch.add(x, y, out=z1) # This is one way
z2 = torch.add(x, y) # This is another way
z = x + y # This is my preferred way, simple and clean.
# -- Subtraction --
z = x - y # We can do similarly as the preferred way of addition
# -- Division (A bit clunky) --
z = torch.true_divide(x, y) # Will do element wise division if of equal shape
# -- Inplace Operations --
t = torch.zeros(3)
t.add_(x) # Whenever we have operation followed by _ it will mutate the tensor in place
t += x # Also inplace: t = t + x is not inplace, bit confusing.
# -- Exponentiation (Element wise if vector or matrices) --
z = x.pow(2) # z = [1, 4, 9]
z = x ** 2 # z = [1, 4, 9]
# -- Simple Comparison --
z = x > 0 # Returns [True, True, True]
z = x < 0 # Returns [False, False, False]
# -- Matrix Multiplication --
x1 = torch.rand((2, 5))
x2 = torch.rand((5, 3))
x3 = torch.mm(x1, x2) # Matrix multiplication of x1 and x2, out shape: 2x3
x3 = x1.mm(x2) # Similar as line above
# -- Matrix Exponentiation --
matrix_exp = torch.rand(5, 5)
print(
matrix_exp.matrix_power(3)
) # is same as matrix_exp (mm) matrix_exp (mm) matrix_exp
# -- Element wise Multiplication --
z = x * y # z = [9, 16, 21] = [1*9, 2*8, 3*7]
# -- Dot product --
z = torch.dot(x, y) # Dot product, in this case z = 1*9 + 2*8 + 3*7
# -- Batch Matrix Multiplication --
batch = 32
n = 10
m = 20
p = 30
tensor1 = torch.rand((batch, n, m))
tensor2 = torch.rand((batch, m, p))
out_bmm = torch.bmm(tensor1, tensor2) # Will be shape: (b x n x p)
# -- Example of broadcasting --
x1 = torch.rand((5, 5))
x2 = torch.ones((1, 5))
z = (
x1 - x2
) # Shape of z is 5x5: How? The 1x5 vector (x2) is subtracted for each row in the 5x5 (x1)
z = (
x1 ** x2
) # Shape of z is 5x5: How? Broadcasting! Element wise exponentiation for every row
# Other useful tensor operations
sum_x = torch.sum(
x, dim=0
) # Sum of x across dim=0 (which is the only dim in our case), sum_x = 6
values, indices = torch.max(x, dim=0) # Can also do x.max(dim=0)
values, indices = torch.min(x, dim=0) # Can also do x.min(dim=0)
abs_x = torch.abs(x) # Returns x where abs function has been applied to every element
z = torch.argmax(x, dim=0) # Gets index of the maximum value
z = torch.argmin(x, dim=0) # Gets index of the minimum value
mean_x = torch.mean(x.float(), dim=0) # mean requires x to be float
z = torch.eq(x, y) # Element wise comparison, in this case z = [False, False, False]
sorted_y, indices = torch.sort(y, dim=0, descending=False)
z = torch.clamp(x, min=0)
# All values < 0 set to 0 and values > 0 unchanged (this is exactly ReLU function)
# If you want to values over max_val to be clamped, do torch.clamp(x, min=min_val, max=max_val)
x = torch.tensor([1, 0, 1, 1, 1], dtype=torch.bool) # True/False values
z = torch.any(x) # will return True, can also do x.any() instead of torch.any(x)
z = torch.all(
x
) # will return False (since not all are True), can also do x.all() instead of torch.all()
3. Tensor Reshaping
x = torch.arange(9)
# Let's say we want to reshape it to be 3x3
x_3x3 = x.view(3, 3)
# We can also do (view and reshape are very similar)
# and the differences are in simple terms (I'm no expert at this),
# is that view acts on contiguous tensors meaning if the
# tensor is stored contiguously in memory or not, whereas
# for reshape it doesn't matter because it will copy the
# tensor to make it contiguously stored, which might come
# with some performance loss.
x_3x3 = x.reshape(3, 3)
# If we for example do:
y = x_3x3.t()
print(
y.is_contiguous()
) # This will return False and if we try to use view now, it won't work!
# y.view(9) would cause an error, reshape however won't
# This is because in memory it was stored [0, 1, 2, ... 8], whereas now it's [0, 3, 6, 1, 4, 7, 2, 5, 8]
# The jump is no longer 1 in memory for one element jump (matrices are stored as a contiguous block, and
# using pointers to construct these matrices). This is a bit complicated and I need to explore this more
# as well, at least you know it's a problem to be cautious of! A solution is to do the following
print(y.contiguous().view(9)) # Calling .contiguous() before view and it works
# Moving on to another operation, let's say we want to add two tensors dimensions togethor
x1 = torch.rand(2, 5)
x2 = torch.rand(2, 5)
print(torch.cat((x1, x2), dim=0).shape) # Shape: 4x5
print(torch.cat((x1, x2), dim=1).shape) # Shape 2x10
# Let's say we want to unroll x1 into one long vector with 10 elements, we can do:
z = x1.view(-1) # And -1 will unroll everything
# If we instead have an additional dimension and we wish to keep those as is we can do:
batch = 64
x = torch.rand((batch, 2, 5))
z = x.view(
batch, -1
) # And z.shape would be 64x10, this is very useful stuff and is used all the time
# Let's say we want to switch x axis so that instead of 64x2x5 we have 64x5x2
# I.e we want dimension 0 to stay, dimension 1 to become dimension 2, dimension 2 to become dimension 1
# Basically you tell permute where you want the new dimensions to be, torch.transpose is a special case
# of permute (why?)
z = x.permute(0, 2, 1)
# Splits x last dimension into chunks of 2 (since 5 is not integer div by 2) the last dimension
# will be smaller, so it will split it into two tensors: 64x2x3 and 64x2x2
z = torch.chunk(x, chunks=2, dim=1)
print(z[0].shape)
print(z[1].shape)
# Let's say we want to add an additional dimension
x = torch.arange(
10
) # Shape is [10], let's say we want to add an additional so we have 1x10
print(x.unsqueeze(0).shape) # 1x10
print(x.unsqueeze(1).shape) # 10x1
# Let's say we have x which is 1x1x10 and we want to remove a dim so we have 1x10
x = torch.arange(10).unsqueeze(0).unsqueeze(1)
# Perhaps unsurprisingly
z = x.squeeze(1) # can also do .squeeze(0) both returns 1x10
That was some essential Tensor operations, hopefully you found it useful!