8.1 PyTorch - framework pro hluboké učení¶
Obsah: *instalace PyTorch
- tensory (vícedimensionální pole)
- konstruktory
- datové typy
- matematické oprace
- PyTorch proměnná typu
Variables(s gradienty) - NumPy <-> PyTorch
- tensory -> GPU -> CPU
torchvision
- příprava dat
* datasety
* pomocné funkce
DatasetaDataLoaderpro trnasformaci data "loading" datasetů pro učení modelu.
PyTorch tensor¶
Tensory jsou základní datové struktury pro reprezentaci data v PyTorch. tensor .. vícedimensionální pole. V kontextu hlubokého uční tensory jsou vektroy, matice arbitrárního počtu dimenzí.
PyTorch zahrnuje nejen definici tensorů, ale i mnoho definovaných matematických funkcí utilit.
PyTorch tensor vs. NumPy pole¶
- jsou podobné
- PyTorch umí využít GPU k akceleraci výpočtů
- v PyTorch je implementován autograd pro automatické derivace funkcí * PyTorch má velké množství základních bloků pro definici širokého množství architektur ANN.
PyTorch tensor API¶
Dokumentce: (https://pytorch.org/docs/stable/index.html).
Tensors: Multidimensional arrays¶
import torch
import numpy as np
torch.__version__
'2.9.1+cu128'
PyTorch Tensors constructors¶
Comparing NumPy and PyTorch
a = np.ones(3)
print(a)
[1. 1. 1.]
Constructors from other containers¶
# passing Python list to te constructor, the same effect!
points = torch.tensor([4.0, 1.0, 5.0, 3.0, 2.0, 1.0])
points
tensor([4., 1., 5., 3., 2., 1.])
# 2D, (list of lists) passed to the constructor
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
points
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
# dimensionality
points.shape
torch.Size([3, 2])
Indexing tensors¶
Tensors uses the same notion. We can use range indexing for each of the tensor's dimensions.
# tensor two indices to access 2D elements
points[0, 1]
tensor(1.)
# the first row
points[0]
tensor([4., 1.])
Tensors storage¶
Values in tensors are allocated in contignuous chunks of memory managed by torch.Storage instances. A storage is a one-dimensional array of numerical data: that is, a contignuous block of memory containing numbers of a given type, such as float (32 bits representing floating-point number). A PyTorch Tensor instance is a view of such a Storage instance that is capable of indexing into that storage using an offset. Multiple tensors can index the same storage even if they index into the data differently.
Each element is a 32-bit (4-byte) float (in the above case). Storing a 1D tensor of 1.000.000 float numbers will require 4.000.000 contignuous bytes plus small overhead for the metadata.
# indexing into the storage
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
points
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
# use method storage() to see the content
points.storage()
/tmp/ipykernel_29909/2552502568.py:2: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly. To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage() points.storage()
4.0 1.0 5.0 3.0 2.0 1.0 [torch.storage.TypedStorage(dtype=torch.float32, device=cpu) of size 6]
# even though the tensor is 3x2, the storage is contignous array of size 6
# get second point in the tensor
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
second_point = points[1]
print(second_point)
tensor([5., 3.])
# size
points.size()
torch.Size([3, 2])
# it is the same information contained in the shape
points.shape
torch.Size([3, 2])
Specifying the numeric type with dtype¶
The dtypeargument to tensor constructors specifies the numerical data (d) type, similar to NumPy.
torch.float.. 32-bit floating point numbertorch.double.. 64-bittorch.float16ortorch.half.. 16-bittorch.int8.. signed 8-bit integerstorch.uint8.. unsigned 8-bittorch.int16ortorch.short.. signed 16-bittorch.int32ortorch.int.. signed 32-bittorch.int64ortorch.long.. signed 64-bittorch.bool.. Boolean
Computations happening in neural networks are typically executed with 32-bit floating-point precision (torch.float or torch.float32).
Tensors can be used as indexes in other tensors, PyTorch expects indexing tensors to have 64-bit integer data type (torch.int64).
Predicates on tensors, such as points > 1.0, produce bool tensors (torch.bool).
Managing a tensor's dtype attribute¶
# float
double_points = torch.ones(10, 2, dtype=torch.double)
# integer
short_points = torch.tensor([[1, 2], [3, 4]], dtype=torch.short)
double_points.dtype
torch.float64
short_points.dtype
torch.int16
# casting
double_points = torch.zeros(10, 2).double()
short_points = torch.ones(10, 2).short()
double_points.dtype
torch.float64
short_points.dtype
torch.int16
# or the more convenient and readble method .to()
double_points = torch.zeros(10, 2).to(torch.double)
short_points = torch.ones(10, 2).to(dtype=torch.short)
# mixing input data types in operations converts to the 'larger' type
points_64 = torch.rand(5, dtype=torch.double) # <1>
points_short = points_64.to(torch.short)
points_64 * points_short # works from PyTorch 1.3 onwards
tensor([0., 0., 0., 0., 0.], dtype=torch.float64)
Random values¶
# NumPy random
np.random.rand(2,2)
# Torch random
torch.rand(2,2)
tensor([[0.4890, 0.3256],
[0.2001, 0.9904]])
Math operations¶
# Element wise addition
a = torch.ones(2,2)
b = torch.ones(2)
c = a + b
c
tensor([[2., 2.],
[2., 2.]])
c = torch.add(a, b)
c
tensor([[2., 2.],
[2., 2.]])
# In-place addition
print(c)
c.add_(a)
tensor([[2., 2.],
[2., 2.]])
tensor([[3., 3.],
[3., 3.]])
# Multiplication: torch.mul(a, b))
# ...
# Tensor Mean
a = torch.Tensor([[1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 12, 13, 14, 15, 16, 17, 18, 19]])
print(a.size())
print(a.mean(dim=1))
torch.Size([2, 9]) tensor([ 5.0000, 14.8889])
Vast majority of operations on tensors are available in the torch module and can be called as methods of a tensor object.
Look in to the web documentation(https://pytorch.org/docs/stable/index.html) for:
Math operations:
- Pointwise ops:
- Reduction ops:
- Comparision ops:
- Spectral ops:
Random sampling
Parallelism
PyTorch Abstraction¶
Tensor: Like array in Numpy, but runs on GPU
Variable: Stores data and gradient; Node in a computational graph;
PyTorch Variables¶
Variables allows us to accumulate gradients!
When using autograd, the forward pass of your network will define a computational graph; nodes in the graph will be Tensors, and edges will be functions that produce output Tensors from input Tensors. PyTorch Tensors can be created as variable objects where a variable represents a node in computational graph.
requires_grad = True
from torch.autograd import Variable
a = Variable(torch.ones(2,2), requires_grad = True)
a
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
# not a variable
torch.ones(2,2)
tensor([[1., 1.],
[1., 1.]])
What is requires_grad?¶
Allows calculation of gradients w.r.t. the variable!
NumPy interoperability¶
PyTorch tensors can be converted to NumPyarrays and vice versa very efficiently. This allows to take advantage of huge swath of functionality in the wider Python ecosystem that has built up around the NumPy array type. The zero-copy interoperabilty with NumPy arrays is due to the storage system working with the Python buffer protocol (https://docs.python.org/3/c-api/buffer.html).
.numpy()
points = torch.ones(3, 4)
points_np = points.numpy()
points_np
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]], dtype=float32)
type(points_np)
numpy.ndarray
We can use such conversions at basically no cost, as long as the data sits in CPU RAM. However, if the tensor is allocated on the GPU, PyTorch will make a copy of the tensor into a NumPy array allocated on the CPU.
from_numpy()
# to torch Tensor
points = torch.from_numpy(points_np)
points
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
points2 = torch.rand(3,4)
points2
tensor([[0.8771, 0.9038, 0.5047, 0.8803],
[0.6451, 0.3752, 0.9187, 0.6939],
[0.5543, 0.8473, 0.4428, 0.0649]])
points2.numpy()
array([[0.87709844, 0.9037786 , 0.50470734, 0.88034207],
[0.6451403 , 0.37524128, 0.9186927 , 0.69393736],
[0.5542964 , 0.8473235 , 0.44275117, 0.06491977]], dtype=float32)
Serializing tensors¶
PyTorch uses pickle under the hood to serialize the tensor object, plus dedicated serialization code for the storage.
points
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
# save our points tensor to a file
torch.save(points, './ourpoints.t')
# we can pass a file descriptor in lieu of the file name
with open('./ourpoints2.t','wb') as f:
torch.save(points, f)
points_in = torch.load('./ourpoints.t')
points_in
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
with open('./ourpoints.t','rb') as f:
points = torch.load(f)
points
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
Moving tensors to the GPU¶
Every PyTorch tensor can be transferred to the GPU(s) in order to perform massively parrallel, fast computatios. In adition to dtype, a PyTorch Tensor also has the notion of device, which is where the computer the tensor data is placed.
torch.cuda.is_available()
False
if torch.cuda.is_available():
points_gpu = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]], device='cuda')
else:
print('CUDA is not available')
CUDA is not available
Také metoda to.
if torch.cuda.is_available():
points_gpu = points.to(device='cuda')
else:
print('CUDA is not available')
CUDA is not available
# specify the number of the GPU device
if torch.cuda.is_available():
points_gpu = points.to(device='cuda:0')
else:
print('CUDA is not available')
CUDA is not available
# Some more GPU operations, if CUDA is installed
if torch.cuda.is_available():
points = 2 * points # <1> on CPU
points_gpu = 2 * points.to(device='cuda') # <2> on GPU
else:
print('CUDA is not available')
CUDA is not available
if torch.cuda.is_available():
points_gpu = points_gpu + 4
if torch.cuda.is_available():
points_cpu = points_gpu.to(device='cpu')
We can also use the shorthand method cpuand cuda instead of the to method.
points_gpu = points.cuda() # <1>
points_gpu = points.cuda(0)
points_cpu = points_gpu.cpu()
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) Cell In[54], line 1 ----> 1 points_gpu = points.cuda() # <1> 2 points_gpu = points.cuda(0) 3 points_cpu = points_gpu.cpu() File ~/workspace/yusu/.direnv/python-3.13.5/lib/python3.13/site-packages/torch/cuda/__init__.py:410, in _lazy_init() 405 raise AssertionError( 406 "libcudart functions unavailable. It looks like you have a broken build?" 407 ) 408 # This function throws if there's a driver initialization error, no GPUs 409 # are found or any other error occurs --> 410 torch._C._cuda_init() 411 # Some of the queued calls may reentrantly call _lazy_init(); 412 # we need to just return without initializing in that case. 413 # However, we must not let any *other* threads in! 414 _tls.is_initializing = True RuntimeError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx