Úvod do strojového učení v DPZ¶
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
Dataset
aDataLoader
pro 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.1.1'
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()
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 dtype
argument to tensor constructors specifies the numerical data (d) type, similar to NumPy.
torch.float
.. 32-bit floating point numbertorch.double
.. 64-bittorch.float16
ortorch.half
.. 16-bittorch.int8
.. signed 8-bit integerstorch.uint8
.. unsigned 8-bittorch.int16
ortorch.short
.. signed 16-bittorch.int32
ortorch.int
.. signed 32-bittorch.int64
ortorch.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.9967, 0.8005], [0.7617, 0.7876]])
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 NumPy
arrays 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.3398, 0.5504, 0.2272, 0.4683], [0.5183, 0.6231, 0.3598, 0.4764], [0.3914, 0.7710, 0.4794, 0.6644]])
points2.numpy()
array([[0.33983666, 0.5503959 , 0.22718483, 0.46827793], [0.5183251 , 0.6230857 , 0.35983914, 0.47641146], [0.39138806, 0.7709591 , 0.47940183, 0.66443247]], 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 cpu
and cuda
instead of the to
method.
points_gpu = points.cuda() # <1>
points_gpu = points.cuda(0)
points_cpu = points_gpu.cpu()
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) Input In [66], in <module> ----> 1 points_gpu = points.cuda() # <1> 2 points_gpu = points.cuda(0) 3 points_cpu = points_gpu.cpu() File /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/torch/cuda/__init__.py:289, in _lazy_init() 284 raise RuntimeError( 285 "Cannot re-initialize CUDA in forked subprocess. To use CUDA with " 286 "multiprocessing, you must use the 'spawn' start method" 287 ) 288 if not hasattr(torch._C, "_cuda_getDeviceCount"): --> 289 raise AssertionError("Torch not compiled with CUDA enabled") 290 if _cudart is None: 291 raise AssertionError( 292 "libcudart functions unavailable. It looks like you have a broken build?" 293 ) AssertionError: Torch not compiled with CUDA enabled