PyTorch とは
概要
Python のオープンソースの機械学習ライブラリ.
PyTorch は Tensor(torch.Tensor
)と呼ばれるクラスを定義しており,多次元配列の保存と演算に利用している.Numpy の配列 Array に似ているが,CUDA が有効な Nvidia の GPU 上での演算も可能になっている.
[参照元] --> PyTorch - Wikipedia
他のライブラリとの違い
機械学習ライブラリは,Define by Run と Define and Run の2種類に大別される.
-
Define by Run
実行しながらネットワークを定義する.
ネットワークを動的に変更できるため,柔軟な設計が可能である.
たとえば,データのサイズによってネットワークを切り替えたり,イテレーションごとに設計変更したりすることも可能.
有名なライブラリは,PyTorch や Chainer など. -
Define and Run
先にネットワークを定義してから実行する.
レゴブロックのようにパーツを組み合わせるだけで簡単にネットワークを構成できる.
簡潔でわかりやすい.
有名なライブラリは,Keras や Tensorflow など.
PyTorch は,実行しながらネットワークを構築する Define by Run の機械学習ライブラリに属する.
このような違いがあるため,これらのライブラリは適材適所で使うのがよさそう.
通常のデータ分析業務では Define and Run の Keras が簡潔であり,詳細な設計が必要な研究や困難なタスクでは Define by Run の PyTorch が優位か.
[参照元] --> 『PyTorch入門』使い方&Tensorflow, Keras等との違いとは? - プロクラシスト
PyTorch と Chainer
PyTorch と Chainer の最大の違いは,PyTorch が海外のコミュニティで盛んに使用されるのに対して,Chainer は日本が主流であること.これは,Chainer が日本発の Preferred Networks (PFN)という会社が開発したことに起因する.
しかしながら,2019年12月に PFN が Chainer のメジャーアップデートを終了し PyTorch の研究開発に移行すると発表したことで,2つのライブラリの関係性は大きく一変した.詳しくはこちら.
そのため,今後 Define by Run の機械学習ライブラリを使用する場合は PyTorch を選択するのが無難だろう.
PyTorch 入門
PyTorch の公式チュートリアルを引用しながら,PyTorch の仕様を確認する.
Tensor の使い方(前編:定義・演算)
What is PyTorch? -- PyTorch Tutorials 1.4.0 documentation
PyTorch で使用する Tensor
は Numpy のndarray
に似ているが,Tensor
は計算の高速化のために GPU を用いて計算できる.
以下では,Numpy と比較しつつ PyTorch のTensor
の使い方をまとめる.
ライブラリのインポート
import torch
import numpy as np
配列の定義
全ての要素がゼロの配列.
# Tensor
x_t = torch.zeros(2, 3)
# Numpy
x_n = np.zeros((2,3))
print('Tensor:\n',x_t,'\n')
print('Numpy:\n',x_n)
# ---Output---
#Tensor:
# tensor([[0., 0., 0.],
# [0., 0., 0.]])
#Numpy:
# [[0. 0. 0.]
# [0. 0. 0.]]
全ての要素が1の配列.
# Tensor
x_t = torch.ones(2,3)
# Numpy
x_n = np.ones((2,3))
print('Tensor:\n',x_t,'\n')
print('Numpy:\n',x_n)
# ---Output---
#Tensor:
# tensor([[1., 1., 1.],
# [1., 1., 1.]])
#Numpy:
# [[1. 1. 1.]
# [1. 1. 1.]]
要素の値を指定した配列.
# Tensor
x_t = torch.tensor([[5,3],[10,6]])
# Numpy
x_n = np.array([[5,3],[10,6]])
print('Tensor:\n',x_t,'\n')
print('Numpy:\n',x_n)
# ---Output---
#Tensor:
# tensor([[ 5, 3],
# [10, 6]])
#Numpy:
# [[ 5 3]
# [10 6]]
要素の値を乱数で指定した配列.
# Tensor
x_t = torch.rand(2,3)
# Numpy
x_n = np.random.rand(2,3)
print('Tensor:\n',x_t,'\n',x12_t,'\n')
print('Numpy:\n',x_n,'\n',x12_n)
# ---Output---
#Tensor:
# tensor([[0.5266, 0.1276, 0.6704],
# [0.0412, 0.5800, 0.0312]])
# tensor(0.3370)
#Numpy:
# [[0.08877971 0.51718009 0.99738679]
# [0.35288525 0.68630145 0.73313903]]
# 0.1799177580940461
配列の要素の取得
配列の各要素へのアクセスはx[0,1]
のように行えばよい(これで,配列x
の1行2列目の要素が取得できる.)
# Tensor
x12_t = x_t[0,1]
# Numpy
x12_n = x_n[0,1]
print('Tensor:\n',x12_t,'\n')
print('Numpy:\n',x12_n)
# ---Output---
#Tensor:
# tensor(0.1276)
#Numpy:
# 0.5171800941956144
ここで注意が必要なのは,配列の要素を取得した際に,Numpy は数値を取得するが,PyTorch では数値ではなく Tensor を取得する点.そのため,PyTorch ではこのように抜き出した配列の要素をそのままスカラー量のように扱うことができない.Numpy と同様に数値を取り出す場合は,Tensor.item()
を実行する必要がある.
x12_value = x12_t.item()
print(x12_t)
print(x12_value)
# ---Output---
# tensor(0.1276)
# 0.12760692834854126
四則演算
PyTorch でも Numpy と同じ感覚で四則演算を行うことができる.
# Tensor
x_t = torch.Tensor([1,2,3])
y_t = torch.Tensor([2,2,2])
add_t = x_t + y_t
sub_t = x_t - y_t
mul_t = x_t * y_t
div_t = x_t / y_t
print('Tensor:\nAddition:\n',add_t,'\nSubtraction:\n',sub_t,
'\nMultiplication:\n',mul_t,'\nDivision:\n',div_t,'\n')
# Numpy
x_n = np.array([1,2,3])
y_n = np.array([2,2,2])
add_n = x_n + y_n
sub_n = x_n - y_n
mul_n = x_n * y_n
div_n = x_n / y_n
print('Numpy:\nAddition:\n',add_n,'\nSubtraction:\n',sub_n,
'\nMultiplication:\n',mul_n,'\nDivision:\n',div_n)
# ---Output---
#Tensor:
#Addition:
# tensor([3., 4., 5.])
#Subtraction:
# tensor([-1., 0., 1.])
#Multiplication:
# tensor([2., 4., 6.])
#Division:
# tensor([0.5000, 1.0000, 1.5000])
#
#Numpy:
#Addition:
# [3 4 5]
#Subtraction:
# [-1 0 1]
#Multiplication:
# [2 4 6]
#Division:
# [0.5 1. 1.5]
Tensor の使い方(後編:変換・自動微分)
Autograd: Automatic Differentiation -- PyTorch Tutorials 1.4.0 documentation
配列の形状操作
配列の形状情報(行数,列数)はshape
メソッドで取得可能.Numpy と同様の挙動を示す.
# Tensor
x_t = torch.rand(4,3)
row_t = x_t.shape[0]
column_t = x_t.shape[1]
print('Tensor:\n','row: ',row_t,'column: ',column_t)
# Numpy
x_n = np.random.rand(4,3)
row_n = x_n.shape[0]
column_n = x_n.shape[1]
print('Numpy:\n','row: ',row_n,'column: ',column_n)
# ---Output---
#Tensor:
# row: 4 column: 3
#Numpy:
# row: 4 column: 3
配列の形状を変更したい場合は,PyTorch では.view()
,Numpy では.reshape()
を使うことが多い.ただし,PyTorch の Tensor に対しても,Numpy と同様に.reshape()
が使用できる.
# Tensor
x_t = torch.rand(4,3)
y_t = x_t.view(12)
z_t = x_t.view(2,-1)
print('Tensor:\n',x_t,'\n',y_t,'\n',z_t,'\n')
# Numpy
x_n = np.random.rand(4,3)
y_n = x_n.reshape(12)
z_n = x_n.reshape([2,-1])
print('Numpy:\n',x_n,'\n',y_n,'\n',z_n)
# ---Output---
#Tensor:
# tensor([[0.5357, 0.2716, 0.2651],
# [0.6570, 0.0844, 0.9729],
# [0.4436, 0.9271, 0.4013],
# [0.8725, 0.2952, 0.1330]])
# tensor([0.5357, 0.2716, 0.2651, 0.6570, 0.0844, 0.9729, 0.4436, 0.9271, 0.4013,
# 0.8725, 0.2952, 0.1330])
# tensor([[0.5357, 0.2716, 0.2651, 0.6570, 0.0844, 0.9729],
# [0.4436, 0.9271, 0.4013, 0.8725, 0.2952, 0.1330]])
#
#Numpy:
# [[0.02711389 0.24172801 0.01202486]
# [0.59552453 0.49906154 0.81377212]
# [0.24744639 0.58570244 0.26464142]
# [0.14519645 0.03607043 0.46616757]]
# [0.02711389 0.24172801 0.01202486 0.59552453 0.49906154 0.81377212
# 0.24744639 0.58570244 0.26464142 0.14519645 0.03607043 0.46616757]
# [[0.02711389 0.24172801 0.01202486 0.59552453 0.49906154 0.81377212]
# [0.24744639 0.58570244 0.26464142 0.14519645 0.03607043 0.46616757]]
.reshape()
を使用した場合.
# Tensor
x_t = torch.rand(4,3)
y_t = x_t.reshape(2,-1)
#y_t = torch.reshape(x_t,[2,-1]) <-- Also works
print('Tensor:\n',y_t,'\n')
# Numpy
x_n = np.random.rand(4,3)
y_n = x_n.reshape(2,-1)
#y_n = np.reshape(x_n,[2,-1]) <-- Also works
print('Numpy:\n',y_n)
# ---Output---
#Tensor:
#tensor([[0.0617, 0.4898, 0.4745, 0.8218, 0.3760, 0.1556],
# [0.3192, 0.5886, 0.8385, 0.5321, 0.9758, 0.8254]])
#
#Numpy:
#[[0.60080911 0.55132561 0.75930606 0.03275005 0.83148483 0.48780054]
# [0.10971541 0.02317271 0.22571149 0.95286975 0.93045979 0.82358474]]
配列の転置は,PyTorch では.transpose()
または.t()
で,Numpy では.transpose()
または.T
で行う.
# Tensor
x_t = torch.rand(3,2)
xt_t = x_t.transpose(0,1)
#xt_t = torch.transpose(x_t,0,1)
#xt_t = x_t.t()
print('Tensor:\n',x_t,'\n',xt_t)
# Numpy
x_n = np.random.rand(3,2)
xt_n = x_n.transpose()
#xt_n = np.transpose(x_n)
#xt_n = x_n.T
print('Numpy:\n',x_n,'\n',xt_n)
# ---Output---
#Tensor:
# tensor([[0.8743, 0.8418],
# [0.6551, 0.2240],
# [0.9447, 0.2824]])
# tensor([[0.8743, 0.6551, 0.9447],
# [0.8418, 0.2240, 0.2824]])
#Numpy:
# [[0.80380702 0.81511741]
# [0.29398279 0.78025418]
# [0.19421487 0.43054298]]
# [[0.80380702 0.29398279 0.19421487]
# [0.81511741 0.78025418 0.43054298]]
Numpy との変換
-
Tensor
-->ndarray
Tensor
からndarray
に変換するときは,Tensor.numpy()
とすればよい.
なお,変換後のndarray
は参照元のTensor
の変更の影響を受けない.(ndarrya
はTensor
のコピーになっている.)連動させる場合は,in-place operation(各関数の最後に_
を付ける.例えばadd_()
.)を使用する必要がある.
a = torch.ones(5)
b = a.numpy()
a = a + 1
print('a = ',a)
print('b = ',b)
# ---Output---
# a = tensor([2., 2., 2., 2., 2.])
# b = [1. 1. 1. 1. 1.]
a = torch.ones(5)
b = a.numpy()
a.add_(1)
#torch.add(a,1,out=a) <-- Same operation
print('a = ',a)
print('b = ',b)
# ---Output---
# a = tensor([2., 2., 2., 2., 2.])
# b = [2. 2. 2. 2. 2.]
-
Tensor
<--ndarray
ndarray
からTensor
に変換するときは,torch.from_numpy(ndarray)
とすればよい.
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print('a = ',a)
print('b = ',b)
# ---Output---
# a = [2. 2. 2. 2. 2.]
# b = tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
CUDA Tensor
Tensor
は.to()
メソッドを使うことで,計算領域を移動させることができる.
これにより,Tensor
を CPU メモリから GPU メモリへ移動し計算を実行することができる.
x = torch.rand(2,3)
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!
# ---Output---
#tensor([[1.1181, 1.1125, 1.3122],
# [1.1282, 1.5595, 1.4443]], device='cuda:0')
#tensor([[1.1181, 1.1125, 1.3122],
# [1.1282, 1.5595, 1.4443]], dtype=torch.float64)
自動微分
torch.Tensor
のrequires_grad
属性をTrue
にすることで,全ての計算履歴を追跡できるようになる.計算が終了した時にbackward()
メソッドを呼び出すことで,自動的に全ての微分を実行してくれる.微分係数はgrad
属性に格納される.
計算履歴の追跡を止めたいときはdetach()
メソッドを呼び出すことで,その先の計算履歴の追跡から切り離される.
個々のTensor
はgrad_fn
属性を持っている.この属性は,Tensor
を作成するFunction
クラスを参照している.(厳密には,ユーザが定義したTensor
はgrad_fn
属性を持っておらず,計算結果のTensor
にgrad_fn
属性が付与される.)
x = torch.ones(2, 2, requires_grad=True)
print(x)
# ---Output---
#tensor([[1., 1.],
# [1., 1.]], requires_grad=True)
y = x + 2
print(y)
# ---Output---
#tensor([[3., 3.],
# [3., 3.]], grad_fn=<AddBackward0>)
print(x.grad_fn)
print(y.grad_fn)
# ---Output---
# None
# <AddBackward0 object at 0x7f2285d93940>]
z = y * y * 3
out = z.mean()
print(z)
print(out)
# ---Output---
#tensor([[27., 27.],
# [27., 27.]], grad_fn=<MulBackward0>)
#tensor(27., grad_fn=<MeanBackward0>)
print(z.grad_fn)
# ---Output---
#<MulBackward0 object at 0x7f2285d93ac8>
out.backward()
print(x.grad)
# ---Output---
#tensor([[4.5000, 4.5000],
# [4.5000, 4.5000]])
最終結果について,計算を実際に行ってみると,
out = \frac{1}{4} \sum_{i} z_i \\
z_i = y_i \cdot y_i \cdot 3 = 3 \cdot (x_i+2)^2
したがって,
\frac{\partial out}{\partial x_i} = \frac{1}{4} \cdot 3 \cdot 2 \cdot (x_i+2) = 4.5
となることが確かめられる.
まとめ
以下にこの記事の要点をまとめる.
- PyTorch は Define by Run の機械学習ライブラリ.
- 高速計算や自動微分が可能な
torch.Tensor
という配列を利用する.これは,Numpy のnumpy.ndaray
とほとんど同じような使い方(定義・演算)ができるほか,簡単に相互変換が行える. -
torch.Tensor
のrequires_grad
属性をTrue
にすることで計算履歴が追跡可能になり,計算終了時にbackward()
メソッドを呼び出すことで自動微分が行われる.これは,ニューラルネットワークの誤差逆伝播法によるパラメータ更新に大いに役立つ.