PyTorch Tutorials : 60 MINUTE BLITZ/PyTorchでのテンソルの扱い
ディープラーニングを行う多くの場面で多次元のテンソルが利用されます。PyTorchではそのテンソルをどのように扱われているのかに触れていきます。
まず必要なライブラリをimportします。
from __future__ import print_function import torch
1.1 Tensors
簡単なテンソル演算を行なっていきます。
x = torch.empty(5, 3) print(x)
出力:
tensor([[ 0.0000e+00, -4.6566e-10, 0.0000e+00], [-4.6566e-10, 1.4013e-44, -0.0000e+00], [ 0.0000e+00, 0.0000e+00, 0.0000e+00], [ 1.4013e-45, 0.0000e+00, 0.0000e+00], [ 0.0000e+00, 0.0000e+00, 0.0000e+00]])
いきなり公式チュートリアルの結果と異なる結果が出ましたが、 torch.empty
では引数に渡される階数・次元の初期化されていないテンソルを生成するものらしく、上記の例では \(5\times 3\) の行列の箱が生成されたとみなして良いでしょう。
例えば torch.rand
のような0から1の一様分布で初期化されたテンソルを生成すると
x = torch.rand(5, 3) print(x)
出力:
tensor([[0.5244, 0.5074, 0.5285], [0.1785, 0.6951, 0.8131], [0.7553, 0.8839, 0.2929], [0.1196, 0.2254, 0.7781], [0.9354, 0.6836, 0.7565]])
と初期化された値が入ります。同様に zeros
では
x = torch.zeros(5, 3, dtype=torch.long) print(x)
出力:
tensor([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
とゼロで初期化された \(5\times 3\) 行列が生成されました。
直で数値データを入力させることもできます。
x = torch.tensor([5.5, 3]) print(x)
出力:
tensor([5.5000, 3.0000])
x = x.new_ones(5, 3, dtype=torch.double) # new_* methods take in sizes print(x)
出力:
tensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], dtype=torch.float64)
上記の例のように階数・次元を変えることもできますし、 dtype=torch.double
で型を明示的に変更することもできるようです。
既に作成されたテンソルに関しては次のようなこともできます。
x = torch.randn_like(x, dtype=torch.float) # override dtype! print(x) # result has the same size
出力:
tensor([[-1.1875, 0.8310, 0.0724], [-1.0569, 0.4325, 0.5735], [-2.0469, -1.1699, 0.1958], [ 1.1205, 1.3053, -0.0773], [-0.6397, 0.6752, -1.0437]])
既に作成されている x
を同じ階数・次元数で正規分布の初期化です。前と同様、型の再定義もできます。
ここでそれぞれ型が何か気になってきたので実験してみます。
x = torch.ones(5, 3) print(x.dtype) x = torch.ones(5, 3, dtype=torch.double) print(x.dtype) x = torch.ones(5, 3, dtype=torch.float) print(x.dtype) x = torch.ones(5, 3, dtype=torch.long) print(x.dtype) x = torch.ones(5, 3, dtype=torch.int) print(x.dtype)
出力:
torch.float32 torch.float64 torch.float32 torch.int64 torch.int32
ほう。デフォルトの設定では32ビットサイズ小数点型であるのに対し、 torch.double
を引数に渡すと64ビットに増えるようですね。また整数型では long
で64ビット、 int
で32ビット。
どこかのサイトでPyTorchの多値分類クラス数は int
ではなく long
であると見た記憶があるので、いずれそれに触れることになるでしょう。
また、階数・次元数は以下のようにして確認することができます。
print(x.size())
出力:
torch.Size([5, 3])
「 size()
があるなら shape
もあるやろ」とやってみると同じ結果が出てきました。
ここまで見てきたように、基本的に関数名も含めTensorはNumpyの ndarray
と同じように扱うことができるようです。
1.2 Operations
ここではテンソルの演算を確認していきます。演算操作にはいくつかの構文が存在します。
最初に加算です。
パターン1
y = torch.rand(5, 3) print(x + y)
出力:
tensor([[-0.2000, 0.8456, 0.8438], [-0.2037, 0.4536, 0.6577], [-1.4763, -0.4680, 0.7430], [ 1.4270, 1.7108, 0.6088], [-0.4684, 0.7397, -0.4239]])
加算の異なる記法が下記の形になります。
パターン2
print(torch.add(x, y))
出力は直前の結果と同じになります。
他にも様々あるので列挙していきます。
パターン3
result = torch.empty(5, 3) torch.add(x, y, out=result) print(result)
パターン4
# adds x to y y.add_(x) print(y)
上記1から4は全て同じ結果になります。
また、変数を破壊的に変更する演算はだいたい演算関数の後に _
をつけると実現されるらしいです。実験してみます。例えば、まだ出てきていませんが .t()
で転置の演算になります。
x = torch.ones(3, 5) print(x) print(x.t()) print(x)
出力:
tensor([[1., 1., 1., 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.]]) tensor([[1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.]])
これを _
をつけて実行すると
x = torch.ones(3, 5) print(x) print(x.t_()) print(x)
出力:
tensor([[1., 1., 1., 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.]]) tensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]])
変数 x
が、その場で転置された状態に置き換えられました。この操作は .add_()
や .copy()
などでも適応できるそうです。
チュートリアル本編に戻ります。
前の節でもあったように、テンソルの操作は基本的にNumpyの操作と同じで
x = torch.randn(4, 4) print(x) print(x[:, 1])
出力:
tensor([[ 0.6680, -0.0047, -0.7369, -0.6312], [ 0.8199, 0.7885, 0.9745, -0.1165], [ 0.6668, -0.5141, 2.0248, -0.1372], [ 1.4586, 0.8823, -0.4660, -0.8134]]) tensor([-0.0047, 0.7885, -0.5141, 0.8823])
ここの本家チュートリアルでは直前の操作の間に何かしらの操作を x
に対して行なっているらしく、謎の値が書かれていますのでご注意ください。
サイズの変更は torch.view
で実行できます。
x = torch.randn(4, 4) y = x.view(16) z = x.view(-1, 8) # the size -1 is inferred from other dimensions print(x.size(), y.size(), z.size())
出力:
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
Numpyと同じはずなので reshape
も試したところ使えました。
print(x.reshape(16).size())
出力:
torch.Size([16])
1.3 NumPy Bridge
NumpyとPyTorchのテンソルは簡単に行き来できる設定になっているようです。
a = torch.ones(5) print(a) b = a.numpy() print(b)
出力:
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
また、メモリも共有されているため、以下のようなことができます(っていうか起こります)。
a.add_(1) print(a) print(b)
出力:
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
a.numpy()
のメモリが b
に引き継がれているので、 a
を変更することで b
も変わってしまいます。
ここまでPyTorch to Numpyを見てきましたが、Numpy to PyTorchも可能です。これまでの操作の逆を一気に実行します。
import numpy as np a = np.ones(5) b = torch.from_numpy(a) np.add(a, 1, out=a) print(a) print(b)
出力:
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
1.4 CUDA Tensors
最後にGPU用に用意されている型に触れます。
もしGPUでCUDAを掴める状態であれば
torch.cuda.is_available()
出力:
True
になります。そしてCUDAを掴める状態であれば以下のコードのif文が通り、
# let us run this cell only if CUDA is available # We will use ``torch.device`` objects to move tensors in and out of GPU if torch.cuda.is_available(): device = torch.device("cuda") # a CUDA device object y = torch.ones_like(x, device=device) # directly create a tensor on GPU x = x.to(device) # or just use strings ``.to("cuda")`` z = x + y print(z) print(z.to("cpu", torch.double)) # ``.to`` can also change dtype together!
出力:
となります。 tensor
には割り振られているCUDA番号がアサインされ表示されます。