Torch: framework pro strojové učení i pro zpracování vektorů a tenzorů

19. 10. 2017
Doba čtení: 21 minut

Sdílet

Ve druhé části seriálu o frameworku Torch dokončíme popis metod pro manipulaci s komponentami tenzorů i se samotnými tenzory. Budeme připraveni na pokračování, v nichž se již budeme zabývat reálnými úlohami.

Obsah

1. Torch: framework pro strojové učení i pro zpracování vektorů a tenzorů (2)

2. Kontinuální uložení komponent tenzorů v operační paměti

3. Operace select aneb vytvoření „řezu“ z tenzoru

4. Metoda select aplikovaná na 2D matice

5. Metoda select aplikovaná na tenzory vyššího řádu

6. Vylepšený operátor indexování

7. Použití operátoru indexování na běžné 1D vektory

8. Použití operátoru indexování pro matice

9. Operátor indexování a tenzory vyšších řádů

10. Získání základních informací o tenzoru – velikost, počet dimenzí, krok mezi řádky atd.

11. Změna typu komponent tenzoru

12. Změna tvaru či velikosti tenzoru (reshape)

13. Změna tvaru tenzoru a metoda isContiguous

14. Změna tvaru tenzoru a metody size, dimstride

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

16. Odkazy na Internetu

1. Torch: framework pro strojové učení i pro zpracování vektorů a tenzorů (2)

V úvodní části začínajícího seriálu o frameworku Torch jsme si mj. řekli, že základní datovou strukturou, s níž se v tomto frameworku pracuje, jsou tenzory. Vektory a matice přitom můžeme v kontextu Torche považovat za speciální případ tenzorů, takže pro ně nebylo nutné navrhovat specializované metody. Dnes si popíšeme zejména operaci select, použití operátorů pro indexování (které vás možná překvapí svými vlastnostmi) a samozřejmě nezapomeneme ani na další operace, především na operaci určenou pro změnu tvaru tenzoru. Taktéž se seznámíme s tím, jak lze o tenzorech získat různé užitečné informace. V navazujícím článku si pak mj. popíšeme i dvě zbývající operace – gather a scatter, které umožňují provádění mnohdy velmi komplikovaných operací s komponentami tenzorů.

Pokud vás zajímá, jak je problematika práce s podobnými datovými strukturami (tenzory, n-dimenzionálními poli) řešena v jiných jazycích, můžete prostudovat možnosti APL a jeho modernější varianty jazyka J.

Při zkoušení dále popisovaných příkladů použijte buď interaktivní prostředí Torche:

$ th
 
  ______             __   |  Torch7
 /_  __/__  ________/ /   |  Scientific computing for Lua.
  / / / _ \/ __/ __/ _ \  |  Type ? for help
 /_/  \___/_/  \__/_//_/  |  https://github.com/torch
                          |  http://torch.ch

th>

nebo si alternativně naklonujte repositář se všemi příklady a poté si je můžete postupně spouštět:

$ th 14_select_3rd_order_tensor.lua

2. Kontinuální uložení komponent tenzorů v operační paměti (2)

V první části seriálu o knihovně Torch 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 Lua, jsou v operační paměti uloženy stejným způsobem, jako klasická pole v programovacím jazyku C. Ovšem některé dále popsané operace 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 Torch přidána metoda contiguous() vracející buď původní tenzor ve chvíli, kdy je v paměti uložen v jediném kontinuálním bloku, popř. vracející kopii tenzoru ve chvíli, kdy nebyl uložen kontinuálně (ovšem kopie již kontinuálně uložena je). Záleží jen na uživateli, zda a v jakém případě tuto metodu použije.

Pokud pouze potřebujete zjistit, jestli jsou komponenty tenzoru uloženy v paměti kontinuálně, použijte metodu nazvanou isContiguous() vracející pravdivostní hodnotu true či false. Použití této metody bude popsáno v navazujících kapitolách.

3. Operace select aneb vytvoření „řezu“ z tenzoru

Jednou z velmi užitečných a často vyžadovaných operací, které je možné s objekty typu Tensor provádět, je tvorba takzvaných „odřezků“ či „řezů“ (slice) provedená metodou nazvanou select (zde se terminologie knihovny Torch odlišuje od některých dalších podobně zaměřených frameworků). Metodě select se předávají dva parametry – dimenze (první, druhá, třetí, …) a index požadovaného řezu v rámci této dimenze:

tensor:select(dimenze, index)

Výsledkem zavolání této metody je nový tenzor (interně se jedná o pohled – view – na tenzor původní), který má o jednu dimenzi méně, než zdrojový tenzor. Z této vlastnosti metody select nepřímo vyplývá i to, že tuto metodu není možné použít na jednorozměrné vektory, i když by se mohlo zdát, že by výsledkem mohl být skalár (proto u vektorů použijte namísto metody select běžné indexování pomocí operátoru []).

4. Metoda select aplikovaná na 2D matice

Podívejme se nyní na některé způsoby použití metody select. Nejprve se zaměříme na 2D matice, které jsou nejjednodušší strukturou, na kterou je možné tuto metodu použít.

Nejdřív vytvoříme matici o rozměrech 5×5 prvků a naplníme její prvky hodnotami od 1 do 25. Postup jsme si již vysvětlili minule:

th> x = torch.Tensor(5,5)
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> s = x:storage()
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> for i = 1,s:size() do
..>     s[i]=i
..> end
                                                                      [0.0002s]
th> x
  1   2   3   4   5
  6   7   8   9  10
 11  12  13  14  15
 16  17  18  19  20
 21  22  23  24  25
[torch.DoubleTensor of size 5x5]
 
                                                                      [0.0005s]

Výběr prvního a posledního řádku se provede jednoduše – metodě select se v prvním parametru předá jednička (první dimenze) v parametru druhém pak index řádku. Povšimněte si, že výsledkem je opět tenzor, tentokrát však jednodimenzionální:

th> x:select(1, 1)
 1
 2
 3
 4
 5
[torch.DoubleTensor of size 5]
 
                                                                      [0.0003s]
th> x:select(1, 5)
 21
 22
 23
 24
 25
[torch.DoubleTensor of size 5]
 
                                                                      [0.0003s]

Mimochodem – stejnou operaci můžeme mnohem jednodušeji provést i prostým operátorem indexování (kterému samozřejmě dávejte přednost, pokud je to možné):

th> x[1]
 1
 2
 3
 4
 5
[torch.DoubleTensor of size 5]
 
                                                                      [0.0002s]
th> x[5]
 21
 22
 23
 24
 25
[torch.DoubleTensor of size 5]
                                                                      [0.0002s]

Následuje výběr prvního a posledního sloupce. Nyní použijeme druhou dimenzi; první parametr metody select tedy bude nastaven na 2 a druhým parametrem bude index sloupce:

th> x:select(2, 1)
  1
  6
 11
 16
 21
[torch.DoubleTensor of size 5]
 
                                                                      [0.0003s]
th> x:select(2, 5)
  5
 10
 15
 20
 25
[torch.DoubleTensor of size 5]
 
                                                                      [0.0003s]

Ještě otestujme způsob uložení komponent zdrojového tenzoru i tenzorů výsledných v operační paměti:

th> x:isContiguous()
true
                                                                      [0.0001s]
th> x:select(1,1):isContiguous()
true
                                                                      [0.0001s]
th> x:select(1,5):isContiguous()
true
                                                                      [0.0001s]
th> x:select(2,1):isContiguous()
false
                                                                      [0.0001s]
th> x:select(2,5):isContiguous()
false
                                                                      [0.0001s]
th> x[1]:isContiguous()
true
                                                                      [0.0001s]

Z výsledků vyplývá, že komponenty 2D tenzorů jsou v operační paměti uloženy po řádcích, ovšem u tenzorů, které vznikly výběrem po sloupcích, tomu tak není.

5. Metoda select aplikovaná na tenzory vyššího řádu

Metodu select samozřejmě můžeme použít i pro tenzory vyšších řádů. Ukažme si to na tenzoru s 4×4×4 komponentami, které postupně nabývají hodnot od 1 do 64. Nejdříve takový tenzor vytvoříme a inicializujeme jeho komponenty:

th> t = torch.Tensor(4,4,4)
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> s = t:storage()
                                                                      [0.0000s]
th>
                                                                      [0.0000s]
th> for i = 1,s:size() do
..>     s[i]=i
..> end
                                                                      [0.0001s]

Výsledný tenzor bude vypadat následovně (se způsobem výpisu „trojrozměrných“ struktur jsme se již seznámili minule, výpis si můžeme představit jako jednotlivé za sebou seřazené listy, z nichž každý obsahuje matici):

th> t
(1,.,.) =
   1   2   3   4
   5   6   7   8
   9  10  11  12
  13  14  15  16
 
(2,.,.) =
  17  18  19  20
  21  22  23  24
  25  26  27  28
  29  30  31  32
 
(3,.,.) =
  33  34  35  36
  37  38  39  40
  41  42  43  44
  45  46  47  48
 
(4,.,.) =
  49  50  51  52
  53  54  55  56
  57  58  59  60
  61  62  63  64
[torch.DoubleTensor of size 4x4x4]

                                                                      [0.0013s]

Nyní vybereme nejdříve první a posléze poslední (čtvrtou) matici, z nichž každá má 4×4 prvky. První dimenze v tomto případě vybírá jednotlivé matice (můžeme si to představit tak, že jednotlivé matice 4×4 prvky jsou umístěny za sebou a tvoří 3D krychli):

th> t:select(1, 1)
  1   2   3   4
  5   6   7   8
  9  10  11  12
 13  14  15  16
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0003s]
th> t:select(1, 4)
 49  50  51  52
 53  54  55  56
 57  58  59  60
 61  62  63  64
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0003s]

Opět si povšimněte, že stejného výsledku dosáhneme použitím operátoru indexování:

th> t[1]
  1   2   3   4
  5   6   7   8
  9  10  11  12
 13  14  15  16
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0003s]
th> t[4]
 49  50  51  52
 53  54  55  56
 57  58  59  60
 61  62  63  64
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0003s]

Zkusme nyní složitější příklad, v němž nejdříve vybereme komponenty ležící v horní vrstvě pomyslné 3D krychle a posléze komponenty ležící naopak v základě té samé krychle:

th> t:select(2, 1)
  1   2   3   4
 17  18  19  20
 33  34  35  36
 49  50  51  52
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0004s]
th> t:select(2, 4)
 13  14  15  16
 29  30  31  32
 45  46  47  48
 61  62  63  64
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0003s]

V další dvojici příkladů získáme matice podél třetí osy (dimenze). První výsledek odpovídá levé straně pomyslné krychle, druhý výsledek pak straně pravé:

th> t:select(3, 1)
  1   5   9  13
 17  21  25  29
 33  37  41  45
 49  53  57  61
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0003s]
th> t:select(3, 4)
  4   8  12  16
 20  24  28  32
 36  40  44  48
 52  56  60  64
[torch.DoubleTensor of size 4x4]
 
                                                                      [0.0004s]

Opět otestujme způsob uložení komponent zdrojového tenzoru i tenzorů výsledných v operační paměti:

th> t:isContiguous()
true
                                                                      [0.0001s]
th> t:select(1,1):isContiguous()
true
                                                                      [0.0001s]
th> t:select(1,4):isContiguous()
true
                                                                      [0.0001s]
th> t:select(2,1):isContiguous()
false
                                                                      [0.0001s]
th> t:select(3,1):isContiguous()
false
                                                                      [0.0001s]

Podle očekávání jsou kontinuálně v paměti umístěny prvky matic 4×4 prvky získané řezem podél první osy.

6. Vylepšený operátor indexování

Minule jsme si popsali metody sub a narrow, k nimž dnes přibyla metoda nazvaná select. Všechny tyto metody slouží k výběru určitých komponent z původního (zdrojového) tenzoru, přičemž výběr může být použit pro čtení nebo i pro zápis, tj. modifikaci původního tenzoru. Ovšem v knihovně Torch je možné při konstrukci výběru použít i na první pohled zcela „obyčejný“ operátor indexování, který uživatelům ve skutečnosti nabízí poměrně široké možnosti použití, jenž v mnoha běžných programovacích jazycích nenajdeme. Při indexování komponent tenzoru se používá tato syntaxe:

[{ dimenze1, dimenze2, dimenze3, ... }]

popř.:

[{ {dimenze1s, dimenze1e}, {dimenze2s, dimenze2e}, {dimenze3s, dimenze3e}, ... }]

Povšimněte si, že ve druhém případě se za všech okolností používají složené závorky uvnitř složených závorek, tj. existuje rozdíl mezi zápisem:

[{ 1,2 }]

a:

[{ {1,2} }]

i když se v obou případech do složených závorek zapíše dvojice celých čísel.

Poznámka: právě z tohoto důvodu se za úvodní dvojicí závorek [{ a před koncovou dvojicí }] používá mezera, která zvýrazní případný vnitřní blok {}.

Poznámka2: zápis {x,y} v jazyce Lua znamená deklaraci tabulky (pole) se dvěma prvky. Navíc lze indexy vynechat, což se interpretuje jako celý sloupec, matice atd. (podle dimenze).

7. Použití operátoru indexování na běžné 1D vektory

Použití operátoru indexování pro běžné jednorozměrné vektory je velmi jednoduché, což si ostatně ukážeme na dalších příkladech. Nejdříve vytvoříme desetiprvkový vektor:

th> v1 = torch.range(1, 10)
                                                                      [0.0001s]
th> v1
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
[torch.DoubleTensor of size 10]
 
                                                                      [0.0003s]

Dále získáme třetí až osmý prvek vektoru (povšimněte si, jak je při slovním popisu výhodné začínat indexy prvků od jedničky):

th> v1[{ {3,8} }]
 3
 4
 5
 6
 7
 8
[torch.DoubleTensor of size 6]
 
                                                                      [0.0003s]

Můžeme získat i jeden jediný prvek, ovšem v knihovně Torch se stále bude jednat o tenzor, i když jednoprvkový (prvního řádu):

th> v1[{ {5,5} }]
 5
[torch.DoubleTensor of size 1]
 
                                                                      [0.0003s]

Ve skutečnosti lze výsledný tenzor získaný operátorem indexování použít na levé straně přiřazovacího výrazu, což je překvapivě mocná konstrukce:

th> v1[{ {3,8} }] =0
                                                                      [0.0002s]
th> v1
  1
  2
  0
  0
  0
  0
  0
  0
  9
 10
[torch.DoubleTensor of size 10]
 
                                                                      [0.0003s]

Na závěr otestujeme, jestli jsou prvky původního vektoru i vektoru získaného operátorem indexování, v paměti uloženy kontinuálně:

th> v1:isContiguous()
true
                                                                      [0.0001s]
th> v1[{ {3,8} }]:isContiguous()
true
                                                                      [0.0001s]

Vidíme, že tomu tak skutečně je.

8. Použití operátoru indexování pro matice

V této kapitole si ukážeme, jak se použije rozšířený operátor indexování pro matice. Nejdříve si vytvoříme matici 5×5 prvků a naplníme ji hodnotami od 1 do 25:

th> x = torch.Tensor(5,5)
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> s = x:storage()
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> for i = 1,s:size() do
..>     s[i]=i
..> end
                                                                      [0.0001s]

Výsledná matice vypadá takto:

th> x
  1   2   3   4   5
  6   7   8   9  10
 11  12  13  14  15
 16  17  18  19  20
 21  22  23  24  25
[torch.DoubleTensor of size 5x5]
 
                                                                      [0.0005s]

Následně z matice získáme druhý až čtvrtý řádek matice. Již víme, že se matice ukládají po řádcích, takže indexování je v tomto případě snadné:

th> x[ {{2,4}} ]
  6   7   8   9  10
 11  12  13  14  15
 16  17  18  19  20
[torch.DoubleTensor of size 3x5]
 
                                                                      [0.0003s]

Získání podmatice 3×3 prvky již vyžaduje nepatrně složitější zápis:

th> x[ {{2,4}, {3,5}} ]
  8   9  10
 13  14  15
 18  19  20
[torch.DoubleTensor of size 3x3]
 
                                                                      [0.0002s]

V dalším výsledku si povšimněte, že se vrátila matice o rozměrech 1×1 prvek a nikoli skalár:

th> x[ {{3}, {3}} ]
 13
[torch.DoubleTensor of size 1x1]
 
                                                                      [0.0002s]

Opět můžeme operátor indexování použít na levé straně výrazu, tentokrát pro vynulování podmatice 4×4 prvky:

th> x[ {{1,4}, {1,4}} ] = 0
                                                                      [0.0001s]
th> x
  0   0   0   0   5
  0   0   0   0  10
  0   0   0   0  15
  0   0   0   0  20
 21  22  23  24  25
[torch.DoubleTensor of size 5x5]
 
                                                                      [0.0004s]

Na závěr si ukážeme nastavení celého (druhého) sloupce na samé devítky. Povšimněte si použití prázdných {} při specifikaci indexu:

th> x[ {{}, {2}} ] = 9
                                                                      [0.0000s]
th> x
  0   9   0   0   5
  0   9   0   0  10
  0   9   0   0  15
  0   9   0   0  20
 21   9  23  24  25
[torch.DoubleTensor of size 5x5]
 
                                                                      [0.0001s]

9. Operátor indexování a tenzory vyšších řádů

Výše popsaný vylepšený operátor indexování je samozřejmě možné použít i u tenzorů vyšších řádů, což si samozřejmě opět vyzkoušíme. Nejprve vytvoříme tenzor s 64 komponentami:

th> t = torch.Tensor(4,4,4)
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> s = t:storage()
                                                                      [0.0001s]
th>
                                                                      [0.0000s]
th> for i = 1,s:size() do
..>     s[i]=i
..> end
                                                                      [0.0002s]

Výsledný tenzor bude vypadat takto:

th> t
(1,.,.) =
   1   2   3   4
   5   6   7   8
   9  10  11  12
  13  14  15  16
 
(2,.,.) =
  17  18  19  20
  21  22  23  24
  25  26  27  28
  29  30  31  32
 
(3,.,.) =
  33  34  35  36
  37  38  39  40
  41  42  43  44
  45  46  47  48
 
(4,.,.) =
  49  50  51  52
  53  54  55  56
  57  58  59  60
  61  62  63  64
[torch.DoubleTensor of size 4x4x4]
 
                                                                      [0.0008s]

Následující příkaz vybere tenzor složený ze dvou matic 4×4 prvky (jedná se o matice s indexy 2 a 3):

th> t[ {{2,3}} ]
(1,.,.) =
  17  18  19  20
  21  22  23  24
  25  26  27  28
  29  30  31  32
 
(2,.,.) =
  33  34  35  36
  37  38  39  40
  41  42  43  44
  45  46  47  48
[torch.DoubleTensor of size 2x4x4]
 
                                                                      [0.0002s]

Další možnosti – výběr dvou řádku z výše zmíněných matic:

th> t[ {{2,3},{3,4}} ]
(1,.,.) =
  25  26  27  28
  29  30  31  32
 
(2,.,.) =
  41  42  43  44
  45  46  47  48
[torch.DoubleTensor of size 2x2x4]
 
                                                                      [0.0001s]

Výběr sloupce ze zbývajících matic:

th> t[ {{2,3},{3,4},{1}} ]
(1,.,.) =
  25
  29
 
(2,.,.) =
  41
  45
[torch.DoubleTensor of size 2x2x1]
 
                                                                      [0.0001s]

Výběr (zdánlivě) jediné komponenty, ve skutečnosti však tenzoru třetího řádu:

th> t[ {{1},{2},{3}} ]
(1,.,.) =
  7
[torch.DoubleTensor of size 1x1x1]
 
                                                                      [0.0001s]
th> t[ {{4},{3},{2}} ]
(1,.,.) =
  58
[torch.DoubleTensor of size 1x1x1]
 
                                                                      [0.0001s]

10. Získání základních informací o tenzoru – velikost, počet dimenzí, krok mezi řádky atd.

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

function printTensorInfo(tensor)
    print("Size:")
    print(tensor:size())
    print("Elements:")
    print(tensor:nElement())
    print("Dimensions:")
    print(tensor:dim())
    print("Stride:")
    print(tensor:stride())
    print("Storage offset:")
    print(tensor:storageOffset())
end

Funkci můžeme otestovat na několika tenzorech různých řádů:

v = torch.range(1, 10)
m = torch.Tensor(5, 6)
t3 = torch.Tensor(4, 5, 6)
t4 = torch.Tensor(2, 3, 4, 5)
 
printTensorInfo(v)
printTensorInfo(m)
printTensorInfo(t3)
printTensorInfo(t4)
 
printTensorInfo(v[{ {5,8} }])
printTensorInfo(m[{ {1,1},{1,1} }])
printTensorInfo(m[{ {2,2},{2,2} }])

Výsledky pro jednotlivé tenzory (ty jsou zvýrazněny).

Běžný vektor s deseti prvky:

torch.range(1, 10)
 
Size:
 10
[torch.LongStorage of size 1]
 
Elements:
10
Dimensions:
1
Stride:
 1
[torch.LongStorage of size 1]
 
Storage offset:
1

Matice 5×6 prvků:

torch.Tensor(5, 6)
 
Size:
 5
 6
[torch.LongStorage of size 2]
 
Elements:
30
Dimensions:
2
Stride:
 6
 1
[torch.LongStorage of size 2]
 
Storage offset:
1

Povšimněte si, že výsledkem metody size()stride jsou opět tenzory, tentokrát obsahující celá čísla!

3D struktura 4×5×6 prvků:

torch.Tensor(4, 5, 6)
 
Size:
 4
 5
 6
[torch.LongStorage of size 3]
 
Elements:
120
Dimensions:
3
Stride:
 30
  6
  1
[torch.LongStorage of size 3]
 
Storage offset:
1

Výsledek stride říká, že dva korespondujícími prvky na sebe položených matic jsou od sebe vzdáleny 30 prvků, zatímco dva prvky umístěné v jedné matici pod sebou jsou od sebe vzdáleny o délku řádku, tj. o šest prvků.

Tenzor čtvrtého řádu:

torch.Tensor(2, 3, 4, 5)
 
Size:
 2
 3
 4
 5
[torch.LongStorage of size 4]
 
Elements:
120
Dimensions:
4
Stride:
 60
 20
  5
  1
[torch.LongStorage of size 4]
 
Storage offset:
1

Pohled na čtyřprvkový vektor (povšimněte si hodnoty Storage offset):

v[{ {5,8} }]
 
Size:
 4
[torch.LongStorage of size 1]
 
Elements:
4
Dimensions:
1
Stride:
 1
[torch.LongStorage of size 1]
 
Storage offset:
5

Pohled na submatici 1×1 prvek:

m[{ {1,1},{1,1} }]
 
Size:
 1
 1
[torch.LongStorage of size 2]
 
Elements:
1
Dimensions:
2
Stride:
 6
 1
[torch.LongStorage of size 2]
 
Storage offset:
1

Pohled na submatici 1×1 prvek (opět se změnil offset v rámci původní struktury):

m[{ {2,2},{2,2} }]
Size:
 1
 1
[torch.LongStorage of size 2]
 
Elements:
1
Dimensions:
2
Stride:
 6
 1
[torch.LongStorage of size 2]
 
Storage offset:
8

11. Změna typu komponent tenzoru

V některých případech budeme potřebovat změnit datový typ komponent tenzoru. Většinou je výchozím typem Double, ovšem například u operací typu gather budeme potřebovat, aby komponenty měly typ Long apod. K tomu můžeme použít metodu type, které se ve formě řetězce předá požadovaný typ.

Přetypovávat budeme prvky vektoru s deseti prvky:

th> v = torch.range(-5, 5)
                                                                      [0.0002s]
th> v
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.DoubleTensor of size 11]
 
                                                                      [0.0004s]

Vlastní přetypování je možné provést následovně (povšimněte si změny hodnot prvků):

th> v2 = v:type("torch.ByteTensor")
                                                                      [0.0002s]
th> v2
 251
 252
 253
 254
 255
   0
   1
   2
   3
   4
   5
[torch.ByteTensor of size 11]
 
                                                                      [0.0002s]
th> v3 = v:type("torch.CharTensor")
                                                                      [0.0001s]
th> v3
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.CharTensor of size 11]
 
                                                                      [0.0002s]
th> v4 = v:type("torch.ShortTensor")
                                                                      [0.0001s]
th> v4
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.ShortTensor of size 11]
 
                                                                      [0.0002s]
th> v5 = v:type("torch.IntTensor")

v6 = v:type("torch.FloatTensor")
v6
                                                                      [0.0022s]
th> v5
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.IntTensor of size 11]
 
                                                                      [0.0003s]
th> v6 = v:type("torch.FloatTensor")
                                                                      [0.0001s]
th> v6
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.FloatTensor of size 11]
 
                                                                      [0.0003s]

Alternativně je možné použít specializované metody byte(), char() atd:

th> v2_ = v:byte()
                                                                      [0.0001s]
th> v2_
 251
 252
 253
 254
 255
   0
   1
   2
   3
   4
   5
[torch.ByteTensor of size 11]
 
                                                                      [0.0002s]
th> v3_ = v:char()
                                                                      [0.0001s]
th> v3_
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.CharTensor of size 11]
 
                                                                      [0.0002s]
th> v4_ = v:short()
                                                                      [0.0001s]
th> v4_
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.ShortTensor of size 11]
 
                                                                      [0.0002s]
th> v5_ = v:int()
                                                                      [0.0001s]
th> v5_
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.IntTensor of size 11]
 
                                                                      [0.0002s]
th> v6_ = v:float()
                                                                      [0.0001s]
th> v6_
-5
-4
-3
-2
-1
 0
 1
 2
 3
 4
 5
[torch.FloatTensor of size 11]
 
                                                                      [0.0002s]

12. Změna tvaru či velikosti tenzoru (reshape)

Velmi užitečnou operací je změna tvaru a/nebo velikosti tenzoru. V některých knihovnách a frameworcích se tato operace jmenuje reshape, v knihovně Torch se však používá jméno resize. Použití této metody je jednoduché – předá se jí požadovaná velikost a tvar tenzoru ve formátu (dim1, dim2, …). 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 24 prvky:

v = torch.range(1, 24)
print(v)

Vektor vypadá následovně:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
[torch.DoubleTensor of size 24]

Nyní z vektoru vytvoříme matici o velikosti 4×6 prvků (čtyři řádky, šest sloupců):

m1 = v:resize(4, 6)
print(m1)

Výsledek:

  1   2   3   4   5   6
  7   8   9  10  11  12
 13  14  15  16  17  18
 19  20  21  22  23  24
[torch.DoubleTensor of size 4x6]

Důležitá poznámka: zavoláním metody resize ve skutečnosti změníme tvar původního vektoru, takže se jedná o (částečně) destruktivní operaci a nikoli o pouhou funkci! Teoreticky můžete psát pouze:

v:resize(4, 6)
print(v)

i když podle oficiální dokumentace není výsledek zaručen.

Samozřejmě nám nic nebrání ve vytvoření matice o velikosti 6×4 prvků:

m2 = v:resize(6, 4)
print(m2)

S výsledkem:

  1   2   3   4
  5   6   7   8
  9  10  11  12
 13  14  15  16
 17  18  19  20
 21  22  23  24
[torch.DoubleTensor of size 6x4]

Můžeme vytvořit i tenzor třetího řádu s 2×3×4=24 komponentami:

t1 = v:resize(2, 3, 4)
print(t1)

S výsledkem:

(1,.,.) =
   1   2   3   4
   5   6   7   8
   9  10  11  12
 
(2,.,.) =
  13  14  15  16
  17  18  19  20
  21  22  23  24
[torch.DoubleTensor of size 2x3x4]

Další varianta:

t2 = v:resize(3, 2, 4)
print(t2)

Výsledek:

(1,.,.) =
   1   2   3   4
   5   6   7   8
 
(2,.,.) =
   9  10  11  12
  13  14  15  16
 
(3,.,.) =
  17  18  19  20
  21  22  23  24
[torch.DoubleTensor of size 3x2x4]

A ještě jedna varianta:

t3 = v:resize(3, 4, 2)
print(t3)

Výsledek:

(1,.,.) =
   1   2
   3   4
   5   6
   7   8
 
(2,.,.) =
   9  10
  11  12
  13  14
  15  16
 
(3,.,.) =
  17  18
  19  20
  21  22
  23  24
[torch.DoubleTensor of size 3x4x2]

Tenzory vyšších řádů:

t4 = v:resize(2, 2, 2, 3)
print(t4)
(1,1,.,.) =
   1   2   3
   4   5   6
 
(2,1,.,.) =
  13  14  15
  16  17  18
 
(1,2,.,.) =
   7   8   9
  10  11  12
 
(2,2,.,.) =
  19  20  21
  22  23  24
[torch.DoubleTensor of size 2x2x2x3]
t5 = v:resize(3, 2, 2, 2)
print(t5)
(1,1,.,.) =
   1   2
   3   4
 
(2,1,.,.) =
   9  10
  11  12
 
(3,1,.,.) =
  17  18
  19  20
 
(1,2,.,.) =
   5   6
   7   8
 
(2,2,.,.) =
  13  14
  15  16
 
(3,2,.,.) =
  21  22
  23  24
[torch.DoubleTensor of size 3x2x2x2]

13. Změna tvaru tenzoru a metoda isContiguous

Zjištění, zda jsou prvky tenzoru se změněným tvarem v operační paměti stále umístěny kontinuálně, je snadné, protože můžeme použít již výše popsanou metodu isContiguous:

th> v = torch.range(1, 24)
                                                                      [0.0002s]
th> v:isContiguous()
true
                                                                      [0.0001s]
th> m1 = v:resize(4, 6)
                                                                      [0.0001s]
th> m1:isContiguous()
true
                                                                      [0.0001s]
th> m2 = v:resize(6, 4)
                                                                      [0.0000s]
th> m2:isContiguous()
true
                                                                      [0.0000s]
th> t1 = v:resize(2, 3, 4)
                                                                      [0.0000s]
th> t1:isContiguous()
true
                                                                      [0.0000s]
th> t2 = v:resize(3, 2, 4)
                                                                      [0.0000s]
th> t2:isContiguous()
true
                                                                      [0.0000s]
th> t3 = v:resize(3, 4, 2)
                                                                      [0.0000s]
th> t3:isContiguous()
true
                                                                      [0.0000s]
th> t4 = v:resize(2, 2, 2, 3)
                                                                      [0.0000s]
th> t4:isContiguous()
true
                                                                      [0.0000s]
th> t5 = v:resize(3, 2, 2, 2)
                                                                      [0.0000s]
th> t5:isContiguous()
true
                                                                      [0.0000s]

Ve skutečnosti se změna tvaru nijak neprojevila na nutnosti reorganizace či kopie (klonování) komponent v paměti.

14. Změna tvaru tenzoru a metody size, dimstride

Zajímavé bude zjistit, jak se změnily vlastnosti tenzoru po modifikaci jeho tvaru. Opět použijeme nám již známou uživatelsky definovanou funkci:

function printTensorInfo(tensor)
    print("Size:")
    print(tensor:size())
    print("Elements:")
    print(tensor:nElement())
    print("Dimensions:")
    print(tensor:dim())
    print("Stride:")
    print(tensor:stride())
    print("Storage offset:")
    print(tensor:storageOffset())
end

Postupně ji budeme aplikovat na původní jednorozměrný vektor i na tenzory, které z něho byly vytvořeny:

Informace o původním vektoru nás už ničím nepřekvapí:

th> v = torch.range(1, 24)
                                                                      [0.0001s]
th> printTensorInfo(v)
Size:
 24
[torch.LongStorage of size 1]
 
Elements:
24
Dimensions:
1
Stride:
 1
[torch.LongStorage of size 1]
 
Storage offset:
1
                                                                      [0.0004s]

Co se stane, když z vektoru vytvoříme matici? Získáme tenzor s naprosto stejnými vlastnostmi, jakoby byl vytvořen přímo nějakým konstruktorem:

bitcoin_skoleni

th> m1 = v:resize(4, 6)
                                                                      [0.0001s]
th> printTensorInfo(m1)
Size:
 4
 6
[torch.LongStorage of size 2]
 
Elements:
24
Dimensions:
2
Stride:
 6
 1
[torch.LongStorage of size 2]
 
Storage offset:
1
                                                                      [0.0003s]

Totéž samozřejmě bude platit i pro tenzor čtvrtého řádu s 3×2×2×2=24 komponentami:

th> t5 = v:resize(3, 2, 2, 2)
                                                                      [0.0001s]
th> printTensorInfo(t5)
Size:
 3
 2
 2
 2
[torch.LongStorage of size 4]
 
Elements:
24
Dimensions:
4
Stride:
 8
 4
 2
 1
[torch.LongStorage of size 4]
 
Storage offset:
1
                                                                      [0.0003s]

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

Všechny demonstrační příklady, které jsme si popsali v předchozích kapitolách, najdete v GIT repositáři dostupném na adrese https://github.com/tisnik/torch-examples.git. Následují odkazy na zdrojové kódy jednotlivých příkladů:

16. Odkazy na Internetu

  1. Stránka projektu Torch
    http://torch.ch/
  2. Torch na GitHubu (několik repositářů)
    https://github.com/torch
  3. Torch (machine learning), Wikipedia
    https://en.wikipedia.org/wi­ki/Torch_%28machine_learnin­g%29
  4. Torch Package Reference Manual
    https://github.com/torch/tor­ch7/blob/master/README.md
  5. Torch Cheatsheet
    https://github.com/torch/tor­ch7/wiki/Cheatsheet
  6. An Introduction to Tensors
    https://math.stackexchange­.com/questions/10282/an-introduction-to-tensors
  7. Differences between a matrix and a tensor
    https://math.stackexchange­.com/questions/412423/dif­ferences-between-a-matrix-and-a-tensor
  8. 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?
  9. BLAS (Basic Linear Algebra Subprograms)
    http://www.netlib.org/blas/
  10. Basic Linear Algebra Subprograms (Wikipedia)
    https://en.wikipedia.org/wi­ki/Basic_Linear_Algebra_Sub­programs
  11. Comparison of deep learning software
    https://en.wikipedia.org/wi­ki/Comparison_of_deep_lear­ning_software
  12. TensorFlow
    https://www.tensorflow.org/
  13. Caffe2 (A New Lightweight, Modular, and Scalable Deep Learning Framework)
    https://caffe2.ai/
  14. PyTorch
    http://pytorch.org/
  15. Seriál o programovacím jazyku Lua
    http://www.root.cz/serialy/pro­gramovaci-jazyk-lua/
  16. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/
  17. LuaJIT – Just in Time překladač pro programovací jazyk Lua (2)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-2/
  18. LuaJIT – Just in Time překladač pro programovací jazyk Lua (3)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-3/
  19. LuaJIT – Just in Time překladač pro programovací jazyk Lua (4)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-4/
  20. LuaJIT – Just in Time překladač pro programovací jazyk Lua (5 – tabulky a pole)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-5-tabulky-a-pole/
  21. LuaJIT – Just in Time překladač pro programovací jazyk Lua (6 – překlad programových smyček do mezijazyka LuaJITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-6-preklad-programovych-smycek-do-mezijazyka-luajitu/
  22. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-7-dokonceni-popisu-mezijazyka-luajitu/
  23. LuaJIT – Just in Time překladač pro programovací jazyk Lua (8 – základní vlastnosti trasovacího JITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-8-zakladni-vlastnosti-trasovaciho-jitu/
  24. LuaJIT – Just in Time překladač pro programovací jazyk Lua (9 – další vlastnosti trasovacího JITu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-9-dalsi-vlastnosti-trasovaciho-jitu/
  25. LuaJIT – Just in Time překladač pro programovací jazyk Lua (10 – JIT překlad do nativního kódu)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-10-jit-preklad-do-nativniho-kodu/
  26. LuaJIT – Just in Time překladač pro programovací jazyk Lua (11 – JIT překlad do nativního kódu procesorů s architekturami x86 a ARM)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-11-jit-preklad-do-nativniho-kodu-procesoru-s-architekturami-x86-a-arm/
  27. LuaJIT – Just in Time překladač pro programovací jazyk Lua (12 – překlad operací s reálnými čísly)
    http://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua-12-preklad-operaci-s-realnymi-cisly/
  28. Lua Profiler (GitHub)
    https://github.com/luafor­ge/luaprofiler
  29. Lua Profiler (LuaForge)
    http://luaforge.net/projec­ts/luaprofiler/
  30. ctrace
    http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/
  31. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  32. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  33. lua2js
    https://www.npmjs.com/package/lua2js
  34. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  35. Lua (programming language)
    http://en.wikipedia.org/wi­ki/Lua_(programming_langu­age)
  36. LuaJIT 2.0 SSA IRhttp://wiki.luajit.org/SSA-IR-2.0
  37. The LuaJIT Project
    http://luajit.org/index.html
  38. LuaJIT FAQ
    http://luajit.org/faq.html
  39. LuaJIT Performance Comparison
    http://luajit.org/performance.html
  40. LuaJIT 2.0 intellectual property disclosure and research opportunities
    http://article.gmane.org/gma­ne.comp.lang.lua.general/58908
  41. LuaJIT Wiki
    http://wiki.luajit.org/Home
  42. LuaJIT 2.0 Bytecode Instructions
    http://wiki.luajit.org/Bytecode-2.0
  43. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  44. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  45. REPL
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  46. The LLVM Compiler Infrastructure
    http://llvm.org/ProjectsWithLLVM/
  47. clang: a C language family frontend for LLVM
    http://clang.llvm.org/
  48. LLVM Backend („Fastcomp“)
    http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html#llvm-backend
  49. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  50. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  51. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators

Autor článku

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