Intro2DL : Pytorchの基礎

2022/09/06に公開

Pytorch

本リポジトリでは、研究用途を中心に多く使用されているPytorchの基礎をまとめようとしたのですが、UvA Deep Learning Tutorials https://github.com/phlippe/uvadlc_notebooks
をほとんど日本語化しただけのようになってしまいました。今後は、このチュートリアルをベースに追加の関数やモデルも実装しながら拡充していこうと思います。

import numpy as np

バージョンの確認

import torch

print(torch.__version__)
1.12.1

seedの設定

pytorchは確率的な操作が含まれます。ここでは再現性を確保するためにシードを固定しておきましょう。

torch.manual_seed(0)
<torch._C.Generator at 0x10c297df0>

Tensorの作成

numpyのarrayと同様にpytorchではtensorを使用します。

tensorの作成方法には以下のように複数種類存在します。

# 所望の形状のTensorを作成
x = torch.Tensor(2, 3, 4)
print(x)
print(x.shape)
tensor([[[0.0000e+00, 3.6893e+19, 1.6655e-27, 0.0000e+00],
         [1.4013e-44, 0.0000e+00, 0.0000e+00, 0.0000e+00],
         [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]],

        [[0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
         [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
         [0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]]])
torch.Size([2, 3, 4])
# zero埋めのTensorを作成
x = torch.zeros(2, 2)
print(x)
print(x.shape)
tensor([[0., 0.],
        [0., 0.]])
torch.Size([2, 2])
# 1埋めのTensorを作成
x = torch.ones(2, 2)
print(x)
print(x.shape)
tensor([[1., 1.],
        [1., 1.]])
torch.Size([2, 2])
# 0~1の範囲の一様分布からランダムにサンプリングした値をもつTensorを作成
x = torch.rand(2, 2)
print(x)
print(x.shape)
tensor([[0.4963, 0.7682],
        [0.0885, 0.1320]])
torch.Size([2, 2])
# 平均0、分散1の正規分布からランダムにサンプリングした値をもつTensorを作成
x = torch.randn(2, 2)
print(x)
print(x.shape)
tensor([[ 0.4033,  0.8380],
        [-0.7193, -0.4033]])
torch.Size([2, 2])
# numpy.arangeと同様なTensorを作成
x = torch.arange(1, 4, 1)
print(x)
print(x.shape)
tensor([1, 2, 3])
torch.Size([3])
# リストと同様の値をもつTensorを作成
x = torch.Tensor([[1, 2], [3, 4]])
print(x)
print(x.shape)
tensor([[1., 2.],
        [3., 4.]])
torch.Size([2, 2])

Tensor2Numpy, Numpy2Tensor

CPU上で可視化等でTensorからNumpy配列へ変換する必要が出てきます。

# array 2 tensor
array = np.array([2, 3])
tensor = torch.from_numpy(array)
print(tensor)
tensor([2, 3])
# tensor 2 array
array_ = tensor.numpy()
print(array_)
[2 3]

Tensor形状の変更

x = torch.arange(0, 6, 1)
print(x)

# 形状の変更
x = x.view(2, 3)
print(x)

# 0と1の次元を入れ替え
x = x.permute(1, 0)
print(x)
tensor([0, 1, 2, 3, 4, 5])
tensor([[0, 1, 2],
        [3, 4, 5]])
tensor([[0, 3],
        [1, 4],
        [2, 5]])

行列積

NNでよく出てくる重みと入力行列との積をみていきます。
適当に入力行列としてX (m \times p)を、重み行列としてW (p \times n)を考えると出力としては、(m \times n)の行列が得られます。

x = torch.arange(6).view(3, 2)
print(x)
print(x.shape)
tensor([[0, 1],
        [2, 3],
        [4, 5]])
torch.Size([3, 2])
w = torch.arange(8).view(2, 4)
print(w)
print(w.shape)
tensor([[0, 1, 2, 3],
        [4, 5, 6, 7]])
torch.Size([2, 4])
o = torch.matmul(x, w)
print(o)
print(o.shape)
tensor([[ 4,  5,  6,  7],
        [12, 17, 22, 27],
        [20, 29, 38, 47]])
torch.Size([3, 4])

自動微分と誤差逆伝播法

pytorchでは自動で計算グラフが構築され、各パラメータの勾配を自動微分とチェーンルールにより計算することで得ることができます。
しかし、Tensorを作成したデフォルトでは、勾配を計算しないように設定されているので構築時または後で勾配を計算するように設定する必要があります。

# ここで、requires_grad==Trueにするにはdtypeをfloatにする必要がある
x = torch.arange(3, dtype=torch.float32)
print(x.requires_grad)
False
# 後から変更する方法
x.requires_grad_(True)
print(x.requires_grad)
True
# 最初に設定する方法
x = torch.arange(3, dtype=torch.float32, requires_grad=True)
print(x.requires_grad)
True

Example

ここで、パラメータをxとして、出力yを最適化したい場合、私たちは勾配\frac{\partial y}{\partial x}を得たいです。ここで、x = (0, 1, 2)とします。

y = mean((x_i + 2)^2 + 3)
x = torch.arange(3, dtype=torch.float32, requires_grad=True)
print(x.requires_grad)

# ここでは簡単のために別々に計算しています
a = x + 2
b = a ** 2
c = b + 3
y = c.mean()

print(y)
True
tensor(12.6667, grad_fn=<MeanBackward0>)

以上で計算できたので、誤差逆伝播法にて勾配を求めます。
.backward()により、requires_grad==TrueのTensorに対して勾配が自動で計算されます。
この結果は手計算でも確認できますが、正しく計算できていることを確認できました。

# backward()前はNone
print(x.grad)
None
y.backward()
print(x.grad)
tensor([1.3333, 2.0000, 2.6667])

GPU CPU MPS

MLの分野ではGPUが非常によく使用されていますが、上記で作成したTensorはすべてCPU上で作成されています。
そのため、物理的に離れたGPU上で操作するためには、明示的に指定してあげる必要があります。

ここでは、MacのGPUであるMPSの例を挙げようと思ったのですが、今のバージョン(1.12.1)でバグが見つかったので今回はCPUのみで行います。
しかし、今後進むにつれて重い計算を行う際にGPUを使用することもあると思うので、ここではCPU上でもGPU上でも動作するようにコードを書いていきましょう。

# GPUなしのローカルのPCで実行しているのでFalseになっています
gpu_avail = torch.cuda.is_available()
print(f"Is the GPU available? {gpu_avail}")

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
print("Device", device)
Is the GPU available? False
Device cpu
# CPUの場合はtensorの右側に何も表示されない
x = torch.arange(3)
x = x.to(device)
print(x)

# ここでは、MacのGPUを使用しているので、device="mps:0"になっていることが確認できます
x = x.to("mps")
print(x)
tensor([0, 1, 2])
tensor([0, 1, 2], device='mps:0')

また、GPUで計算を行う際は以下の設定をすることで計算の再現性が取れるようにしましょう。
この設定をしてもGPUが物理的に異なる場合は結果も合わないですが、同じGPUを使用したときに再現できるようにするためです。

if torch.cuda.is_available(): 
    torch.cuda.manual_seed(42)
    torch.cuda.manual_seed_all(42)

torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

Discussion