Pseudo Theory of Everything

データサイエンス初心者物理学徒の奮闘記

PyTorch Tutorials : 60 MINUTE BLITZ/PyTorchでのテンソルの扱い

What is 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 であると見た記憶があるので、いずれそれに触れることになるでしょう。

データ型 dtypeへの記述 対応するPython/NumPyのデータ型
Boolean(真偽値) torch.bool bool/np.bool
8-bitの符号なし整数 torch.uint8 int/np.uint8
8-bitの符号付き整数 torch.int8 int/np.int8
16-bitの符号付き整数 torch.int16 / torch.short int/np.uint16
32-bitの符号付き整数 torch.int32 / torch.int int/np.uint32
64-bitの符号付き整数 torch.int64 / torch.long int/np.uint64
16-bitの浮動小数 torch.float16 / torch.half float/np.float16
32-bitの浮動小数 torch.float32 / torch.float float/np.float32
64-bitの浮動小数 torch.float64 / torch.double float/np.float64

 

また、階数・次元数は以下のようにして確認することができます。

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([0.1174], device='cuda:0')
tensor([0.1174], dtype=torch.float64)

となります。 tensor には割り振られているCUDA番号がアサインされ表示されます。