Manipulace s tenzory v knihovně PyTorch

7. 11. 2024
Doba čtení: 27 minut

Sdílet

 Autor: Pixabay
Vysvětlíme si, jak jsou tenzory uloženy v paměti počítače nebo TPU. Taktéž si ukážeme získání pohledu (view) na tenzor a následně si popíšeme i různé operace, které je možné s tenzory provádět jako s celkem.

Obsah

1. Manipulace s tenzory v knihovně PyTorch

2. Získání tvaru tenzoru (shape)

3. Způsob uložení tenzorů v paměti počítače nebo TPU

4. Získání typu prvků tenzoru

5. Specifikace či změna typu prvků tenzoru

6. Manipulace s objektem typu Storage

7. Způsob uložení prvků tenzoru druhého a třetího řádu

8. Konstrukce řezu (slice)

9. Specifikace záporných indexů a záporného kroku při provádění řezu

10. Operace řezu aplikovaná na matice (tenzory druhého řádu)

11. Výběr průsečíku řádků a sloupců jedinou operací řezu

12. Výběr průsečíků řádků a sloupců se specifikací kroku

13. Výběr je pouze pohledem (view) na původní tenzor

14. Druhý pohled na způsob uložení prvků tenzoru: atributy stride, storage_offset a is_contiguous

15. Atributy stride a storage_offset pro řezy vektorů

16. Atributy stride a storage_offset pro řezy matic

17. Změna tvaru tenzoru operací reshape

18. Obsah navazujícího článku

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Manipulace s tenzory v knihovně PyTorch

V dnešním článku o knihovně PyTorch si vysvětlíme, jakým způsobem jsou tenzory uloženy v paměti počítače, resp. alternativně v paměti GPU (nebo spíše TPU – Tensor Processing Unit). Se způsobem uložení do jisté míry souvisí i získání pohledu (view) na tenzor; příkladem je operace řezu (slice). A následně si popíšeme i různé operace, které je možné s tenzory provádět jako s celkem. V této oblasti se jedná především o operaci změny tvaru tenzoru (shape); tato operace se tedy logicky jmenuje reshape.

2. Získání tvaru tenzoru (shape)

Tvar tenzoru neboli shape je popsán n-ticí obsahující počet prvků v jednotlivých dimenzích (ovšem pokud se tvar tenzoru čte, získáme hodnotu typu torch.Size, v níž je n-tice zabalená). Poněkud speciálním případem je skalár (vektor nultého řádu), který je popsán prázdnou n-ticí. Vektor je popsán n-ticí s jednou hodnotou – délkou vektoru, matice n-ticí se dvěma hodnotami atd. atd. Můžeme si to velmi snadno otestovat:

import torch
 
# konstrukce tenzoru + zjisteni jejich tvaru
 
# tenzor nulteho radu - skalar
s1 = torch.tensor(100)
print(s1.shape)
 
# tenzor prvniho radu - vektor
v1 = torch.Tensor(1)
print(v1.shape)
 
v2 = torch.Tensor(3)
print(v2.shape)
 
# tenzor druheho radu - matice
m1 = torch.Tensor(3, 4)
print(m1.shape)
 
# tenzor tretiho radu - 3D pole
c1 = torch.Tensor(3, 4, 5)
print(c1.shape)

Po spuštění tohoto demonstračního příkladu by se měly zobrazit následující tvary:

torch.Size([])
torch.Size([1])
torch.Size([3])
torch.Size([3, 4])
torch.Size([3, 4, 5])

Specifikaci tvaru tenzoru založenou na sekvenci celých čísel jsme využili například v konstruktoru zeros:

# konstrukce tenzoru prvniho radu, vyplneni nulami 
v1 = torch.zeros(1)
print(v1)
print()
 
# konstrukce tenzoru prvniho radu, vyplneni nulami 
v2 = torch.zeros(10)
print(v2)
print()
 
# konstrukce tenzoru druheho radu, vyplneni nulami 
m1 = torch.zeros(3, 4)
print(m1)
print()
 
# konstrukce tenzoru tretiho radu, vyplneni nulami 
c1 = torch.zeros(3, 4, 5)
print(c1)
print()

Výsledky:

tensor([0.])
 
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
 
tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])
 
tensor([[[0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.]],
 
        [[0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.]],
 
        [[0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.],
         [0., 0., 0., 0., 0.]]])

Dtto pro konstruktor ones:

import torch
 
# konstrukce tenzoru, vyplneni jednickami
v1 = torch.ones(1)
print(v1)
print()
 
# konstrukce tenzoru prvniho radu, vyplneni jednickami 
v2 = torch.ones(10)
print(v2)
print()
 
# konstrukce tenzoru druheho radu, vyplneni jednickami 
m1 = torch.ones(3, 4)
print(m1)
print()
 
# konstrukce tenzoru tretiho radu, vyplneni jednickami 
c1 = torch.ones(3, 4, 5)
print(c1)
print()

Výsledky:

tensor([1.])
 
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
 
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
 
tensor([[[1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.]],
 
        [[1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.]],
 
        [[1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.],
         [1., 1., 1., 1., 1.]]])

3. Způsob uložení tenzorů v paměti počítače nebo TPU

V úvodním článku jsme si řekli, že objekty typu Tensor pro uložení svých komponent (prvků) interně používají jednorozměrná pole. To, že se tenzory uživateli jeví jako 1D, 2D, 3D atd. struktury je ve skutečnosti záležitostí pohledů (views) na zmíněná jednorozměrná pole (jedná se vlastně o běžná céčková pole). Pro některé nízkoúrovňové operace může být výhodné přistupovat přímo k internímu poli, což nám knihovna PyTorch umožňuje, protože nabízí objekty typu Storage. Existuje přitom několik konkrétních typů Storage, protože interní (céčková) pole taktéž mohou obsahovat prvky různých typů. Implicitně se používá FloatStorage, pokud není z typu prvků tenzorů zřejmé, že se má jednat o jiný typ (v budoucnu ovšem takto nízkoúrovňový přístup již nebude možný, o čemž se ještě zmíníme v dalších článcích):

Typ Storage Význam
torch.BoolStorage pole s prvky typu boolean
torch.CharStorage pole s prvky typu char
   
torch.ByteStorage pole s prvky typu unsigned char
torch.ShortStorage pole s prvky typu short (16bitové celé číslo)
torch.IntStorage pole s prvky typu int (32bitové celé číslo)
torch.LongStorage pole s prvky typu long (64bitové celé číslo)
   
torch.BFloat16Storage pole s prvky typu brain floating point/Bfloat16
torch.HalfStorage pole s prvky typu half dle IEEE 754
torch.FloatStorage pole s prvky typu float dle IEEE 754
torch.DoubleStorage pole s prvky typu double dle IEEE 754
   
torch.ComplexFloatStorage komplexní hodnoty založené na typu float
torch.ComplexDoubleStorage komplexní hodnoty založené na typu double
   
torch.QUInt8Storage kvantované hodnoty interně uložené do bajtu
torch.QInt8Storage kvantované hodnoty interně uložené do bajtu
torch.QUInt2×4Storage kvantované hodnoty interně uložené do bajtu
torch.QUInt4×2Storage kvantované hodnoty interně uložené do bajtu
torch.QInt32Storage kvantované hodnoty interně uložené do 32bitového slova
   
torch.UntypedStorage bez specifikace typu (jen s tímto typem se setkáme v budoucnu)

4. Získání typu prvků tenzoru

Ještě než se podíváme na to, jakým způsobem je možné zjistit interní pole, které reprezentuje tenzor v paměti, si ukažme způsob zjištění typů prvku tenzoru. Bude se tedy jednat o informaci důležitou při „vysokoúrovňovém“ pohledu na tenzor, kdy nás nezajímají implementační detaily. Pro získání, jakého typu je každý prvek tenzoru, stačí přečíst atribut nazvaný dtype:

import torch
 
# konstrukce tenzoru s inicializaci prvku
v1 = torch.tensor(10)
print(v1)
print(type(v1))
print(v1.dtype)
 
print()
 
# konstrukce tenzoru bez inicializace prvku
m1 = torch.Tensor(3, 4)
print(m1)
print(type(m1))
print(m1.dtype)

V prvním případě (jedná se o tenzor prvního řádu s jedním prvkem) je typ tohoto prvku nastaven na int64, což odpovídá předané hodnotě. Na druhou stranu by možná bylo možné použít i typ s menší bitovou šířkou:

tensor(10)
<class 'torch.Tensor'>
torch.int64

Ve druhém případě (tenzor druhého řádu, neboli v našem podání běžná 2D matice) je, možná poněkud překvapivě, použit typ float32, resp. přesněji řečeno torch.float32 (a prvky tenzoru nejsou inicializovány, takže mohou obsahovat náhodné hodnoty):

tensor([[-2.4020e-35,  4.5600e-41, -2.4020e-35,  4.5600e-41],
        [ 0.0000e+00,  0.0000e+00,  0.0000e+00,  0.0000e+00],
        [ 1.5520e-33,  2.2060e-38, -2.2407e+29,  4.8069e-07]])
<class 'torch.Tensor'>
torch.float32

5. Specifikace či změna typu prvků tenzoru

Typy, které mají mít všechny prvky tenzoru, lze specifikovat již při jeho konstrukci. Ukažme si to na jednoduchém příkladu s tenzorem nultého řádu neboli skalárem. Budeme chtít, aby prvek byl typu bfloat16 (viz též tento článek):

# konstrukce tenzoru nulteho radu (skalaru)
v1 = torch.tensor(10, dtype=torch.bfloat16)
print(v1)
print(type(v1))
print(v1.dtype)

Výsledný tenzor, který získáme, by měl vypadat následovně:

tensor(10., dtype=torch.bfloat16)

torch.bfloat16

Ovšem v případě, že již máme k dispozici zkonstruovaný tenzor, znamená změna typu jeho prvků vlastně vytvoření nového tenzoru (opět – buď v paměti počítače nebo TPU). Tato konverze tenzorů se provádí metodou type, které předáme požadovaný typ prvků nově vytvořeného tenzoru:

# konstrukce tenzoru prvniho radu
v2 = torch.arange(10, 20).type(torch.bfloat16)
print(v2)
print(type(v2))
print(v2.dtype)
 
print()
 
# konstrukce tenzoru druheho radu se zmenou typu prvku
m1 = torch.Tensor(3, 4).type(torch.bfloat16)
print(m1)
print(type(m1))
print(m1.dtype)

Z výsledků je patrné, že se konverze skutečně podařila a že v prvním případě došlo ke konverzi hodnot (ve druhém případě taktéž, ovšem tenzor nebyl naplněn žádnými daty):

tensor([10., 11., 12., 13., 14., 15., 16., 17., 18., 19.],
       dtype=torch.bfloat16)
<class 'torch.Tensor'>
torch.bfloat16
 
tensor([[ 1.6213e+19,  0.0000e+00,  1.6213e+19,  0.0000e+00],
        [ 1.7090e-02,  8.0889e-32,  1.1581e-22, -1.0402e+10],
        [ 8.3200e-32,  1.8529e-21,  3.8738e+20,  1.6941e-20]],
       dtype=torch.bfloat16)
<class 'torch.Tensor'>
torch.bfloat16

6. Manipulace s objektem typu Storage

Ukažme si nyní, jak je možné pro existující tenzor získat objekt typu Storage a jak se dá tento objekt využít pro změnu hodnot komponent tenzoru. Nejprve vytvoříme běžný vektor s deseti prvky:

v1 = torch.tensor([1, 255, 65535, 65536])
print(v1)
print(type(v1))
print(v1.dtype)

Vypíše se:

tensor([    1,   255, 65535, 65536])
<class 'torch.Tensor'>
torch.int64

Následně pro tento vektor získáme objekt typu Storage a vypíšeme si jeho obsah (tedy data tvořící zdroj pro samotný tenzor):

# informace o typu Storage
storage = v1.untyped_storage()
 
print(storage)
print(type(storage))

Z vypsaných informací je patrné (resp. můžeme relativně snadno odvodit), že je tenzor uložen v paměti počítače (a ne TPU) i způsob uložení jeho čtyř prvků:

 1
 0
 0
 0
 0
 0
 0
 0
 255
 0
 0
 0
 0
 0
 0
 0
 255
 255
 0
 0
 0
 0
 0
 0
 0
 0
 1
 0
 0
 0
 0
 0
[torch.storage.UntypedStorage(device=cpu) of size 32]
<class 'torch.storage.UntypedStorage'>

Jednotlivé prvky (rozepsané na bajty) jsou tedy uloženy takto:

   1   0   0   0   0   0   0   0 = 1
 255   0   0   0   0   0   0   0 = 255
 255 255   0   0   0   0   0   0 = 255*256 + 255 = 65535
   0   0   1   0   0   0   0   0 = 1*256*256 = 65536

7. Způsob uložení prvků tenzoru druhého a třetího řádu

Zajímavé (a později i užitečné) bude zjištění, jakým způsobem jsou interně uloženy prvky tenzorů druhého a třetího řádu. Nejprve si ukažme tenzory druhého řádu, které můžeme pro naše potřeby považovat za běžné matice. Zde se nabízí dva možné způsoby uložení – po řádcích nebo po sloupcích. Vytvořme si tedy jednoduchý tenzor pouze se šesti prvky. Navíc budeme ukládat pouze celá osmibitová čísla, což nám čtení výsledků ještě více zjednoduší:

import torch
 
# konstrukce tenzoru druheho radu
m2 = torch.tensor([[1,2,3], [4,5,6]]).type(torch.uint8)
print(m2)
print(type(m2))
print(m2.dtype)
 
print()
 
# informace o typu Storage
storage = m2.untyped_storage()
 
print(storage)
print(type(storage))

Tenzor je nejdříve vypsán ve své původní podobě:

tensor([[1, 2, 3],
        [4, 5, 6]], dtype=torch.uint8)
<class 'torch.Tensor'>
torch.uint8

Na dalších řádcích výstupu se zobrazí interní pohled na storage, ze kterého je vidět uspořádání po řádcích (tedy „podle céčka“, nikoli „podle Fortanu“):

 
 1
 2
 3
 4
 5
 6
[torch.storage.UntypedStorage(device=cpu) of size 6]
<class 'torch.storage.UntypedStorage'>

Podobně můžeme postupovat pro tenzory třetího řádu, tedy z našeho pohledu pro 3D pole:

import torch
 
# konstrukce tenzoru tretiho radu
c1 = torch.tensor([[[1,2,3], [4,5,6]], [[7,8,9], [10,11,12]]]).type(torch.int16)
print(c1)
print(type(c1))
print(c1.dtype)
 
print()
 
# informace o typu Storage
storage = c1.untyped_storage()
 
print(storage)
print(type(storage))

Výpis obsahu tenzoru:

tensor([[[ 1,  2,  3],
         [ 4,  5,  6]],
 
        [[ 7,  8,  9],
         [10, 11, 12]]], dtype=torch.int16)
<class 'torch.Tensor'>
torch.int16

Interní uložení prvků tenzoru:

 1
 0
 2
 0
 3
 0
 4
 0
 5
 0
 6
 0
 7
 0
 8
 0
 9
 0
 10
 0
 11
 0
 12
 0
[torch.storage.UntypedStorage(device=cpu) of size 24]
<class 'torch.storage.UntypedStorage'>

Každý prvek je v tomto případě uložen v šestnácti bitech (tj. na osmi bajtech), takže se vlastně jedná o následující pole:

 1 0
 2 0
 3 0
 4 0
 5 0
 6 0
 7 0
 8 0
 9 0
 10 0
 11 0
 12 0

Z výše uvedeného je patrné, že jsou prvky ukládány v pořadí od nejnižší dimenze k dimenzi nejvyšší. Tomu je nutné přizpůsobit i algoritmy.

8. Konstrukce řezu (slice)

Další zajímavou a velmi užitečnou operací, která produkuje pohled (view) nad tenzorem, je operace nazvaná slice (původně sub. Ta umožňuje z tenzoru získat určitou část, s níž bude možné pracovat jako s dalším tenzorem. Nejprve se podíváme na nejjednodušší použití této operace při práci s vektory. Vytvoříme si vektor s deseti prvky od 1/1 do 1/10:

# konstrukce tenzoru
v1 = torch.range(10, 20)
print(v1)

Vektor skutečně obsahuje deset prvků:

tensor([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])

Pomocí operace slice můžeme získat pohled na prvky ležícími mezi specifikovaným dolním a horním indexem. Třetí až osmý prvek se tedy přečte takto:

# konstrukce rezu z tenzoru prvniho radu
v2 = v1[2:8]
print(v2)

Povšimněte si, že výsledkem operace slice je opět plnohodnotný tenzor.

tensor([12, 13, 14, 15, 16, 17])
Poznámka: prvek s nejvyšším specifikovaným indexem není do výsledného tenzoru zahrnut.

Můžeme specifikovat i krok, tedy vlastně rozdíl v indexech prvků z původního tenzoru:

# konstrukce rezu z tenzoru prvniho radu
v3 = v1[2:8:2]
print(v3)

S výsledkem:

tensor([12, 14, 16])

9. Specifikace záporných indexů a záporného kroku při provádění řezu

Při zadávání indexů prvků je možné použít i záporné hodnoty. Jak je zvykem, jsou tyto hodnoty chápány jako indexy od konce vektoru. Další příklad nám tedy dá stejný výsledek, jako příklad předchozí (druhý prvek od začátku až druhý prvek od konce; povšimněte si, proč by v praxi bylo více konzistentní pracovat s indexy začínajícími od 1 a nikoli od 0):

# konstrukce rezu z tenzoru prvniho radu
v4 = v1[-8:-2]
print(v4)

Výsledek:

tensor([12, 13, 14, 15, 16, 17])

Horní nebo dolní index lze vynechat. Za dolní index se v tomto případě dosadí nula a za horní index počet prvků tenzoru:

# konstrukce rezu z tenzoru prvniho radu
v5 = v1[:-2]
print(v5)
 
# konstrukce rezu z tenzoru prvniho radu
v6 = v1[-3:]
print(v6)

Nyní budou výsledky vypadat následovně:

tensor([10, 11, 12, 13, 14, 15, 16, 17])
tensor([17, 18, 19])

V případě, že je dolní index větší než index horní, vrátí se prázdný tenzor (což je zcela legitimní datová struktura):

# konstrukce rezu z tenzoru prvniho radu
v7 = v1[10:0]
print(v7)

Výsledný tenzor:

tensor([], dtype=torch.int64)

Mohlo by se zdát, že se PyTorch v případě řezů chová stejně, jako knihovna NumPy. Ovšem v případě záporného kroku tomu tak není:

# konstrukce rezu z tenzoru prvniho radu
v8 = v1[10:1:-1]
print(v8)

Nyní dojde k vyhození výjimky:

Traceback (most recent call last):
  File "/home/ptisnovs/xy/src/tensor_slice_operation_1.py", line 32, in <module<
    v8 = v1[10:1:-1]
         ~~^^^^^^^^^
ValueError: step must be greater than zero

Proč tomu tak je, si vysvětlíme v navazujícím textu.

10. Operace řezu aplikovaná na matice (tenzory druhého řádu)

Operaci slice je samozřejmě možné aplikovat i na matice a tenzory vyšších řádů; výsledkem je vždy opět tenzor. Nejprve si pro ukázku vytvoříme 2D matici a naplníme ji prvky s hodnotami 1 až 16:

# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)

Matice se skutečně vytvořila:

tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])

Pokusme se nyní vytvořit řez obsahující řádky 1 a 2 (vyšší index není do výsledku zahrnut):

# konstrukce rezu - pres radky
m2 = m1[1:3]
print(m2)

Výsledný tenzor by měl vypadat následovně:

tensor([[ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.]])

To ovšem není zdaleka vše, protože je možné specifikovat indexy i ve druhé dimenzi. Následující příkaz tedy získá tenzor tvořený původním sloupcem číslo 1 a 2 (indexuje se od nuly):

# konstrukce rezu - pres sloupce
m3 = m1[:,1:3]
print(m3)

Přesvědčme se, že tomu tak skutečně je:

tensor([[ 2.,  3.],
        [ 6.,  7.],
        [10., 11.],
        [14., 15.]])
Poznámka: i v tomto případě lze použít záporné indexy:
import torch
 
# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)
 
print()
 
# konstrukce rezu - pres radky
m2 = m1[-3:-1]
print(m2)
 
print()
 
# konstrukce rezu - pres sloupce
m3 = m1[:,-3:-1]
print(m3)

Výsledky získané po spuštění tohoto skriptu:

tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])
 
tensor([[ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.]])
 
tensor([[ 2.,  3.],
        [ 6.,  7.],
        [10., 11.],
        [14., 15.]])

11. Výběr průsečíku řádků a sloupců jedinou operací řezu

Operace pro výběr celých řádků nebo sloupců, kterou jsme si ukázali v předchozí kapitole, je dokonce možné zkombinovat a vybrat z tenzoru druhého řádu (zde se to ukazuje nejlépe) průsečík několika řádků a sloupců. Výsledkem většinou opět bude tenzor druhého řádu.

Opět si to ukažme na tenzoru, který reprezentuje matici 4×4 prvky:

# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)
 
print()

Tento tenzor je zobrazen následujícím způsobem (což by ostatně pro nás nemělo být nic nového):

tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])

Nyní se můžeme pokusit vybrat podmatici tvořenou průsečíkem druhého a třetího řádku a současně druhého a třetího sloupce. Tuto operaci zapíšeme následujícím způsobem:

# konstrukce rezu - pres radky i sloupce
m2 = m1[1:3, 1:3]
print(m2)

Výsledkem bude matice 2×2 prvky:

tensor([[ 6.,  7.],
        [10., 11.]])

Opět pochopitelně můžeme při specifikaci indexů vynechat první nebo poslední index:

# konstrukce rezu - pres radky i sloupce
m3 = m1[:3, :3]
print(m3)
 
print()
 
# konstrukce rezu - pres radky i sloupce
m4 = m1[1:, 1:]
print(m4)

Výsledky těchto dvou operací budou podmatice 3×3 prvky. Každá z těchto matic bude pochopitelně odlišná:

tensor([[ 1.,  2.,  3.],
        [ 5.,  6.,  7.],
        [ 9., 10., 11.]])
 
tensor([[ 6.,  7.,  8.],
        [10., 11., 12.],
        [14., 15., 16.]])

12. Výběr průsečíků řádků a sloupců se specifikací kroku

I při výběru podmatice tvořené průsečíky řádků a sloupců můžeme specifikovat krok, tedy offset mezi řádky a/nebo sloupci. Implicitní hodnota kroku je (podle očekávání) rovna jedné, ovšem můžeme například zvolit krok 2. Opět si to ukážeme na jednoduchém příkladu. Začneme maticí 4×4 prvky, kterou již dobře známe:

# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)

Výběr každého druhého sloupce a každého druhého řádku s vrácením jen těch prvků, které leží na průsečících:

# konstrukce rezu - pres radky i sloupce
m2 = m1[::2, ::2]
print(m2)

Výsledkem je opět matice o velikosti 2×2, tentokrát se však striktně řečeno nejedná o podmatici:

tensor([[ 1.,  3.],
        [ 9., 11.]])

Podobný výběr, tentokrát však začneme na druhém sloupci a druhém řádku:

# konstrukce rezu - pres radky i sloupce
m3 = m1[1::2, 1::2]
print(m3)

Výsledkem bude odlišná matice o velikosti 2×2 prvky:

tensor([[ 6.,  8.],
        [14., 16.]])

13. Výběr je pouze pohledem (view) na původní tenzor

V předchozím textu jsem napsal, že výsledkem řezu aplikovaného na tenzor je jiný tenzor. To je sice pravda, ovšem oba tenzory budou sdílet svoje data (storage). Co to ovšem znamená v praxi? Pokud budeme tenzory pouze číst, nepoznáme rozdíl, ovšem zápis do jednoho z těchto tenzorů povede k tomu, že se změní i prvky ve druhém (či prvním) tenzoru. Ukažme si to na příkladu tenzoru prvního řádu – vektoru:

# konstrukce tenzoru
v1 = torch.arange(10, 20)
print(v1)

Získáme tento vektor:

tensor([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])

Z tohoto vektoru si přečteme řez:

# konstrukce rezu
v2 = v1[5:10]
print(v2)

Řez vypadá jako nový nezávislý tenzor:

tensor([15, 16, 17, 18, 19])

Do původního vektoru provedeme zápis – změníme jeho prvek:

# modifikace vektoru
v1[5] = 999
print(v1)
print(v2)

Z výpisů je patrné, že se změnil jak původní vektor, tak i řez:

tensor([ 10,  11,  12,  13,  14, 999,  16,  17,  18,  19])
tensor([999,  16,  17,  18,  19])

Nyní naopak budeme modifikovat řez:

# modifikace rezu
v2[0] = 0
print(v1)
print(v2)

Podle očekávání se v tomto případě opět změní jak řez (nový tenzor), tak i původní vektor (tenzor):

tensor([10, 11, 12, 13, 14,  0, 16, 17, 18, 19])
tensor([ 0, 16, 17, 18, 19])

Ovšem totéž chování lze pozorovat i u řezů maticemi, popř. tenzory ještě vyšších řádů. Nyní tedy jen v krátkosti:

import torch
 
# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)
 
print()
 
# konstrukce rezu - pres radky i sloupce
m2 = m1[1:3, 1:3]
print(m2)
 
print()
 
# modifikace rezu
m2[0,0] = 99
m2[0,1] = 99
m2[1,0] = 99
m2[1,1] = 99
print(m2)
 
print()
 
# vypis puvodniho tenzoru
print(m1)

Výsledky (původní tenzor – matice – je zobrazen na posledním místě):

tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])
 
tensor([[ 6.,  7.],
        [10., 11.]])
 
tensor([[99., 99.],
        [99., 99.]])
 
tensor([[ 1.,  2.,  3.,  4.],
        [ 5., 99., 99.,  8.],
        [ 9., 99., 99., 12.],
        [13., 14., 15., 16.]])

A modifikace řezů vytvořených na základě kroku, který je vyšší než jeho implicitní hodnota (1):

import torch
 
# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)
 
print()
 
# konstrukce rezu - pres radky i sloupce
m2 = m1[::2, ::2]
print(m2)
 
print()
 
# konstrukce rezu - pres radky i sloupce
m3 = m1[1::2, 1::2]
print(m3)
 
print()
 
# modifikace rezu
m2[0,0] = 99
m2[0,1] = 99
m2[1,0] = 99
m2[1,1] = 99
print(m2)
 
print()
 
# vypis puvodniho tenzoru
print(m1)
 
print()
 
# modifikace rezu
m3[0,0] = -99
m3[0,1] = -99
m3[1,0] = -99
m3[1,1] = -99
print(m3)
 
print()
 
# vypis puvodniho tenzoru
print(m1)

Z výsledků je patrné, jak se původní matice mění, a to nepřímo – modifikacemi jejích řezů:

tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])
 
tensor([[ 1.,  3.],
        [ 9., 11.]])
 
tensor([[ 6.,  8.],
        [14., 16.]])
 
tensor([[99., 99.],
        [99., 99.]])
 
tensor([[99.,  2., 99.,  4.],
        [ 5.,  6.,  7.,  8.],
        [99., 10., 99., 12.],
        [13., 14., 15., 16.]])
 
tensor([[-99., -99.],
        [-99., -99.]])
 
tensor([[ 99.,   2.,  99.,   4.],
        [  5., -99.,   7., -99.],
        [ 99.,  10.,  99.,  12.],
        [ 13., -99.,  15., -99.]])

14. Druhý pohled na způsob uložení prvků tenzoru: atributy stride, storage_offset a is_contiguous

V prvním článku o knihovně PyTorch jsme si řekli, že tenzory vytvářené základními konstruktory, mj. tenzor s nulovými komponentami, tenzor s komponentami nastavenými na jedničku, vektor vytvořený konstruktorem range či tenzor vytvořený z tabulek (polí) jazyka Python, jsou v operační paměti uloženy stejným způsobem, jako klasická pole v programovacím jazyku C. Ovšem některé popsané operace (například operace řezu) mohou vracet jen vybrané komponenty, které již v obecných případech nemusí v operační paměti ležet kontinuálně za sebou. To sice z pohledu uživatele nepředstavuje větší problém (s výsledkem se stále pracuje jako s plnohodnotným tenzorem), ovšem některé operace se mohou provádět pomaleji, zejména u rozsáhlejších tenzorů. Mj. i z tohoto důvodu byla do knihovny PyTorch přidána metoda is_contiguous(), která zjišťuje, zda jsou prvky tenzoru (či pohledu na něj) uloženy za sebou či nikoli.

Několik dalších metod pak slouží k získání základních informací o daném tenzoru. Především lze zjistit velikost tenzoru, počet dimenzí, počet elementů, offset mezi sousedními komponentami, řádky, maticemi … (stride) a taktéž offset první komponenty v rámci „pohledu“ (storageOffset).

Zajímavé bude zjistit základní vlastnosti tenzorů různých řádů, které ovšem vznikly tak, že jsou v nich prvky uloženy kontinuálně za sebou:

import torch
 
# konstrukce tenzoru
v1 = torch.arange(10, 20)
print("v1:")
print(v1)
print("Stride:", v1.stride())
print("Offset:", v1.storage_offset())
print("Contiguous:", v1.is_contiguous())
print()
 
# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print("m1:")
print(m1)
print("Stride:", m1.stride())
print("Offset:", m1.storage_offset())
print("Contiguous:", m1.is_contiguous())
print()
 
# 3D struktura
m3 = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10, 20, 30], [40, 50, 60], [70, 80, 90]]]
 
# konstrukce tenzoru tretiho radu
c1 = torch.Tensor(m3)
print("c1:")
print(c1)
print("Stride:", c1.stride())
print("Offset:", c1.storage_offset())
print("Contiguous:", c1.is_contiguous())

Všechny tyto tenzory mají Contiguous==True a nulový offset. Hodnota stride odpovídá rozdílům offsetů prvků v jednotlivých dimenzích (po sobě, mezi řádky, mezi maticemi):

v1:
tensor([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
Stride: (1,)
Offset: 0
Contiguous: True
 
m1:
tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])
Stride: (4, 1)
Offset: 0
Contiguous: True
 
c1:
tensor([[[ 1.,  2.,  3.],
         [ 4.,  5.,  6.],
         [ 7.,  8.,  9.]],
 
        [[10., 20., 30.],
         [40., 50., 60.],
         [70., 80., 90.]]])
Stride: (9, 3, 1)
Offset: 0
Contiguous: True

15. Atributy stride a storage_offset pro řezy vektorů

Mnohem zajímavější situace vznikne u tenzorů (resp. pohledů na ně), které vznikly operací řezu. Zde totiž již může být offset větší než nula (když řez nezačíná přesně na prvním prvku původního tenzoru) a stride může být odlišný od jedničky tehdy, pokud je použit explicitně nastavený krok při konstrukci řezu. A se stride odlišným od jedničky pak souvisí i hodnota is_contiguous, která bude nastavena na False:

import torch
 
# konstrukce tenzoru
v1 = torch.arange(10, 20)
print("v1:")
print(v1)
print("Stride:", v1.stride())
print("Offset:", v1.storage_offset())
print("Contiguous:", v1.is_contiguous())
print()
 
# konstrukce tenzoru - rezu
v2 = v1[:5]
print("v2:")
print(v2)
print("Stride:", v2.stride())
print("Offset:", v2.storage_offset())
print("Contiguous:", v2.is_contiguous())
print()
 
# konstrukce tenzoru - rezu
v3 = v1[5:10]
print("v3:")
print(v3)
print("Stride:", v3.stride())
print("Offset:", v3.storage_offset())
print("Contiguous:", v3.is_contiguous())
print()
 
# konstrukce tenzoru - rezu
v4 = v1[::3]
print("v4:")
print(v4)
print("Stride:", v4.stride())
print("Offset:", v4.storage_offset())
print("Contiguous:", v4.is_contiguous())
print()
 
# konstrukce tenzoru - rezu
v5 = v1[2::3]
print("v5:")
print(v5)
print("Stride:", v5.stride())
print("Offset:", v5.storage_offset())
print("Contiguous:", v5.is_contiguous())
print()

Pojďme si nyní jednotlivé případy okomentovat.

Původní vektor, který má pochopitelně nulový offset a stride==1:

v1:
tensor([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
Stride: (1,)
Offset: 0
Contiguous: True

Řez, který začíná na prvním prvku původního vektoru a má krok rovný jedné, má stejné vlastnosti, jako původní vektor:

v2:
tensor([10, 11, 12, 13, 14])
Stride: (1,)
Offset: 0
Contiguous: True

Řez, který začíná až na pátém prvku původního vektoru má offset nastaven na 5 a ne na nulu:

v3:
tensor([15, 16, 17, 18, 19])
Stride: (1,)
Offset: 5
Contiguous: True

Zajímavá situace nastane při konstrukci řezu s krokem 3. Offset stále zůstává na nule, ale stride se pochopitelně odlišuje od jedničky, čemuž odpovídá i atribut is_contiguous:

v4:
tensor([10, 13, 16, 19])
Stride: (3,)
Offset: 0
Contiguous: False

Kombinace obou předchozích případů – nenulový offset a hodnota stride odlišná od jedničky:

v5:
tensor([12, 15, 18])
Stride: (3,)
Offset: 2
Contiguous: False

16. Atributy stride a storage_offset pro řezy matic

Podívejme se ještě, jak jsou vlastně nastaveny atributy stride a storage_offset pro matice a především pro výsledky řezů těmito maticemi. Opět si nejprve uvedeme celý skript a poté okomentujeme výsledky:

import torch
 
# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print("m1:")
print(m1)
print("Stride:", m1.stride())
print("Offset:", m1.storage_offset())
print("Contiguous:", m1.is_contiguous())
print()
 
m2 = m1[1:3]
print("m2:")
print(m2)
print("Stride:", m2.stride())
print("Offset:", m2.storage_offset())
print("Contiguous:", m2.is_contiguous())
print()
 
m3 = m1[:,1:3]
print("m3:")
print(m3)
print("Stride:", m3.stride())
print("Offset:", m3.storage_offset())
print("Contiguous:", m3.is_contiguous())
print()
 
m4 = m1[1:3,1:3]
print("m4:")
print(m4)
print("Stride:", m4.stride())
print("Offset:", m4.storage_offset())
print("Contiguous:", m4.is_contiguous())
print()

Původní matice se svými atributy. Zajímavý je atribut stride, který říká, že rozdíl offsetů mezi prvky, které leží pod sebou (na jiném řádku) je roven čtyřem, ale rozdíl offsetů, které leží vedle sebe na stejném řádku je stále roven jedné:

m1:
tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])
Stride: (4, 1)
Offset: 0
Contiguous: True

Řez po řádcích vede k tomu, že se změní offset (bude ukazovat na v pořadí pátý prvek), ovšem hodnota stride bude stále nastavena na původní hodnotu (4, 1):

m2:
tensor([[ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.]])
Stride: (4, 1)
Offset: 4
Contiguous: True

Výsledek řezu po sloupcích. Offset ukazuje na prvek s hodnotou 2, což asi není překvapivé. Zajímavější je stride, který má stále stejnou hodnotu, což například znamená, že rozdíl offsetů mezi prvky 2 a 6 (nebo 3 a 7) je roven čtyřem a ne dvěma, jak by se mohlo zdát. Zde se tedy naznačuje, že stále interně používáme stejný tenzor, pouze na něj máme odlišný pohled:

m3:
tensor([[ 2.,  3.],
        [ 6.,  7.],
        [10., 11.],
        [14., 15.]])
Stride: (4, 1)
Offset: 1
Contiguous: False

A konečně výsledek řezu po řádcích i sloupcích, kdy je výsledkem podmatice. Offset ukazuje na prvek s hodnotou 6, stride se v tomto případě nezměnil:

m4:
tensor([[ 6.,  7.],
        [10., 11.]])
Stride: (4, 1)
Offset: 5
Contiguous: False

17. Změna tvaru tenzoru operací reshape

Velmi užitečnou operací je změna tvaru a/nebo velikosti tenzoru. V mnoha knihovnách a frameworcích se tato operace jmenuje reshape, v knihovně Torch se však používalo jiné jméno resize. V PyTorchi již opět reshape nalezneme – jedná se o metodu tenzoru. Použití této metody je jednoduché – předá se jí požadovaná velikost a tvar tenzoru ve formátu (dim1, dim2, …), což je zápis n-tice. Pozor si musíme dát pouze na to, že pokud bude mít nový tenzor více komponent, než tenzor původní, nemusí být nově přidané komponenty správně inicializovány (nemusí být nulové). Opět se podívejme na demonstrační příklad. Nejprve si vytvoříme obyčejný jednorozměrný vektor s dvanácti prvky, jehož tvar budeme měnit a získávat tak nové tenzory s odlišnými tvary:

import torch
 
# konstrukce tenzoru, vyplneni sekvenci
v1 = torch.range(1, 12)
print(v1)
print()
 
# zmena tvaru tenzoru
m1 = torch.reshape(v1, (4, 3))
print(m1)
print()
 
# zmena tvaru tenzoru
m2 = torch.reshape(v1, (3, 4))
print(m2)
print()
 
# zmena tvaru tenzoru
m3 = torch.reshape(v1, (3, 2, 2))
print(m3)
print()
 
# zmena tvaru tenzoru
m4 = torch.reshape(v1, (2, 3, 2))
print(m4)
print()
 
# zmena tvaru tenzoru
m5 = torch.reshape(v1, (2, 2, 3))
print(m5)
print()

Skript po svém spuštění vypíše výsledné tenzory s různými tvary (a tím pádem i různých řádů), které z původního vektoru vznikly:

tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12.])
 
tensor([[ 1.,  2.,  3.],
        [ 4.,  5.,  6.],
        [ 7.,  8.,  9.],
        [10., 11., 12.]])
 
tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.]])
 
tensor([[[ 1.,  2.],
         [ 3.,  4.]],
 
        [[ 5.,  6.],
         [ 7.,  8.]],
 
        [[ 9., 10.],
         [11., 12.]]])
 
tensor([[[ 1.,  2.],
         [ 3.,  4.],
         [ 5.,  6.]],
 
        [[ 7.,  8.],
         [ 9., 10.],
         [11., 12.]]])
 
tensor([[[ 1.,  2.,  3.],
         [ 4.,  5.,  6.]],
 
        [[ 7.,  8.,  9.],
         [10., 11., 12.]]])

Počet dimenzí (řád tenzoru) se ovšem může i snížit:

import torch
 
# konstrukce tenzoru
m1 = torch.Tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
print(m1)
print()
 
v1 = torch.reshape(m1, (16, ))
print(v1)

S výsledkem:

ict ve školství 24

tensor([[ 1.,  2.,  3.,  4.],
        [ 5.,  6.,  7.,  8.],
        [ 9., 10., 11., 12.],
        [13., 14., 15., 16.]])
 
tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12., 13., 14.,
        15., 16.])

18. Obsah navazujícího článku

V navazujícím článku dokončíme popis vlastností tenzorů. Ukážeme si totiž, jaké operace je možné s tenzory provádět, ať již se jedná o operace prováděné prvek po prvku, o takzvaný broadcasting nebo o komplikovanější činnosti. Potom již budeme mít všechny znalosti nutné pro využití tenzorů pro konstrukci a trénink neuronových sítí.

19. Repositář s demonstračními příklady

Všechny demonstrační příklady využívající knihovnu PyTorch lze nalézt v repositáři https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady:

# Příklad Stručný popis Adresa příkladu
1 tensor_constructor_scalar1.py konstrukce tenzoru nultého a prvního řádu https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_scalar1.py
2 tensor_constructor_scalar2.py inicializace tenzoru prvního řádu s jedním prvkem https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_scalar2.py
3 tensor_constructor_vector1.py konstrukce tenzoru prvního řádu (tříprvkový vektor) https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_vector1.py
4 tensor_constructor_vector2.py konstrukce tenzoru prvního řádu s inicializací prvků https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_vector2.py
5 tensor_constructor_vector3.py konstrukce tenzoru prvního řádu s využitím generátoru range https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_vector3.py
6 tensor_constructor_matrix1.py vytvoření a inicializace tenzoru druhého řádu, který může být reprezentován maticí https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_matrix1.py
7 tensor_constructor_matrix2.py inicializace prvků matice https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_matrix2.py
8 tensor_constructor_3D1.py tenzor třetího řádu reprezentovaný „3D maticí“ https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_3D1.py
9 tensor_constructor_3D2.py tenzor třetího řádu reprezentovaný „3D maticí“ (jiná forma inicializace) https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_3D2.py
       
10 tensor_constructor_scalar_zero.py vynulování prvků tenzoru nultého řádu https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_scalar_ze­ro.py
11 tensor_constructor_vector_zero.py vynulování prvků tenzoru prvního řádu https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_vector_ze­ro.py
12 tensor_constructor_matrix_zero.py vynulování prvků tenzoru druhého řádu https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_matrix_ze­ro.py
13 tensor_constructor_3D_zero.py vynulování prvků tenzoru třetího řádu https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_constructor_3D_zero.py
       
14 tensor_zeros_shape.py použití konstruktoru zeros pro tenzory různých řádů a tvarů https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_zeros_shape.py
15 tensor_ones_shape.py použití konstruktoru ones pro tenzory různých řádů a tvarů https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_ones_shape.py
16 tensor_eye.py konstrukce jednotkové matice https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/tensor_eye.py
       
17 tensor_range.py využití konstruktoru range https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_range.py
18 tensor_arange.py využití konstruktoru arange https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_arange.py
       
19 tensor_shape.py zjištění tvaru tenzoru https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_shape.py
20 tensor_zeros_shape.py zjištění tvaru tenzoru vytvořeného konstruktorem zeros https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_zeros_shape.py
21 tensor_ones_shape.py zjištění tvaru tenzoru vytvořeného konstruktorem ones https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_ones_shape.py
22 tensor_read_dtype.py zjištění, jakého typu jsou prvky tenzoru https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_read_dtype.py
23 tensor_set_dtype.py nastavení či změna typu prvků tenzoru https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_set_dtype.py
       
24 tensor_storage1.py získání datové struktury se zdrojem dat pro tenzor https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_storage1.py
25 tensor_storage2.py získání datové struktury se zdrojem dat pro tenzor https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_storage2.py
26 tensor_storage3.py získání datové struktury se zdrojem dat pro tenzor https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_storage3.py
27 tensor_storage_casts.py přetypování datové struktury se zdrojem dat pro tenzor https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_storage_casts.py
       
28 tensor_slice_operation1.py konstrukce řezu z tenzoru prvního řádu https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_slice_operation1.py
29 tensor_slice_operation2.py konstrukce řezu z tenzoru druhého řádu (přes řádky a sloupce) https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_slice_operation2.py
30 tensor_slice_operation3.py konstrukce řezu s jeho následnou modifikací https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_slice_operation3.py
31 tensor_slice_operation4.py konstrukce řezu s jeho následnou modifikací (odlišné operace od předchozího příkladu) https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_slice_operation4.py
32 tensor_is_slice.py test základních vlastností řezů https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_is_slice.py
       
33 tensor_stride1.py význam atributů stride a storage_offset https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_stride1.py
34 tensor_stride2.py význam atributů stride a storage_offset https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_stride2.py
35 tensor_stride3.py význam atributů stride a storage_offset https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_stride3.py
       
36 tensor_reshape.py změna tvaru tenzoru operací reshape https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_reshape.py
37 tensor_reshape2.py změna tvaru tenzoru operací reshape https://github.com/tisnik/most-popular-python-libs/blob/master/PyTorch/ten­sor_reshape2.py

20. Odkazy na Internetu

  1. Seriál Programovací jazyk Lua na Rootu:
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  2. PDM: moderní správce balíčků a virtuálních prostředí Pythonu:
    https://www.root.cz/clanky/pdm-moderni-spravce-balicku-a-virtualnich-prostredi-pythonu/
  3. Interní reprezentace numerických hodnot: od skutečného počítačového pravěku po IEEE 754–2008:
    https://www.root.cz/clanky/interni-reprezentace-numerickych-hodnot-od-skutecneho-pocitacoveho-praveku-po-ieee-754–2008/
  4. Interní reprezentace numerických hodnot: od skutečného počítačového pravěku po IEEE 754–2008 (dokončení):
    https://www.root.cz/clanky/interni-reprezentace-numerickych-hodnot-od-skutecneho-pocitacoveho-praveku-po-ieee-754–2008-dokonceni/
  5. Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla:
    https://www.root.cz/clanky/brain-floating-point-ndash-novy-format-ulozeni-cisel-pro-strojove-uceni-a-chytra-cidla/
  6. Stránky projektu PyTorch:
    https://pytorch.org/
  7. Informace o instalaci PyTorche:
    https://pytorch.org/get-started/locally/
  8. Tenzor (Wikipedia):
    https://cs.wikipedia.org/wiki/Tenzor
  9. Introduction to Tensors:
    https://www.youtube.com/wat­ch?v=uaQeXi4E7gA
  10. Introduction to Tensors: Transformation Rules:
    https://www.youtube.com/wat­ch?v=j6DazQDbEhQ
  11. Tensor Attributes:
    https://pytorch.org/docs/sta­ble/tensor_attributes.html
  12. Tensors Explained Intuitively: Covariant, Contravariant, Rank :
    https://www.youtube.com/wat­ch?v=CliW7kSxxWU
  13. What is the relationship between PyTorch and Torch?:
    https://stackoverflow.com/qu­estions/44371560/what-is-the-relationship-between-pytorch-and-torch
  14. What is a tensor anyway?? (from a mathematician):
    https://www.youtube.com/wat­ch?v=K7f2pCQ3p3U
  15. Visualization of tensors – part 1 :
    https://www.youtube.com/wat­ch?v=YxXyN2ifK8A
  16. Visualization of tensors – part 2A:
    https://www.youtube.com/wat­ch?v=A95jdIuUUW0
  17. Visualization of tensors – part 2B:
    https://www.youtube.com/wat­ch?v=A95jdIuUUW0
  18. What the HECK is a Tensor?!?:
    https://www.youtube.com/wat­ch?v=bpG3gqDM80w
  19. Stránka projektu Torch
    http://torch.ch/
  20. Torch na GitHubu (několik repositářů)
    https://github.com/torch
  21. Torch (machine learning), Wikipedia
    https://en.wikipedia.org/wi­ki/Torch_%28machine_learnin­g%29
  22. Torch Package Reference Manual
    https://github.com/torch/tor­ch7/blob/master/README.md
  23. Torch Cheatsheet
    https://github.com/torch/tor­ch7/wiki/Cheatsheet
  24. An Introduction to Tensors
    https://math.stackexchange­.com/questions/10282/an-introduction-to-tensors
  25. Differences between a matrix and a tensor
    https://math.stackexchange­.com/questions/412423/dif­ferences-between-a-matrix-and-a-tensor
  26. Qualitatively, what is the difference between a matrix and a tensor?
    https://math.stackexchange­.com/questions/1444412/qu­alitatively-what-is-the-difference-between-a-matrix-and-a-tensor?
  27. Tensors for Neural Networks, Clearly Explained!!!:
    https://www.youtube.com/wat­ch?v=L35fFDpwIM4
  28. Tensor Processing Unit:
    https://en.wikipedia.org/wi­ki/Tensor_Processing_Unit
  29. Třída Storage:
    http://docs.pytorch.wiki/en/sto­rage.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.