import torch device = "cuda" if torch.cuda.is_available() else "cpu" # Cuda to run on GPU!
# 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)
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()
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!