🔍

PyTorch入門に入門してみた - その1

2023/08/19に公開

はじめに

私は趣味で機械学習や深層学習を学んでいるものなので、結構ざっくりした理解で進めていきます。詳細を知りたい方はご自身でドキュメンテーションを参照したりしてください。
(私自身の勉強にもなりますので、浮かんだ疑問点やアドバイス(?)など積極的にコメントしていただけると幸いです)

今回やること

PyTorchの公式ドキュメントにあるチュートリアル(英語版)日本語翻訳バージョンをやってみた上での私なりのメモのような記事です。皆さんが学習する上で参考になれば幸いです。
(日本語バージョンを制作してくださった小川さん、本当にありがとうございます)
それでは、実際にやっていきましょう!!

PyTorch入門1. テンソル

原著はこちら
日本語解説はこちら

テンソルとは

TensorはPyTorchで使用する多次元配列のデータ構造で、NumPyにおけるndarraysに似ているのでndarraysを使い慣れている人にとっては扱いやすい(らしい)
PyTorchでディープなニューラルネットワークを扱うために用いられるため、
*CPUだけでなくGPU上での高速計算に対応している(NumPyはCPU上でのみ計算される)
*自動微分に最適化されている(??)
GPUがなぜ深層学習に使われるのか(速いのか)について、NVIDIA社のインタビュー記事が読みやすかったので是非)

テンソルの初期化

1. PythonのリストからTensorへの変換

data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
# 型のチェック
print(type(data))
print(type(x_data))

"""
Output
<class 'list'>
<class 'torch.Tensor'>
"""

2. NumPy arrayからTensorへの変換

np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(type(np_array))
print(type(x_np))

"""
<class 'numpy.ndarray'>
<class 'torch.Tensor'>
"""

3. 他のTensorからTensorへの変換
すでに存在するTensorを引数として渡すことで、そのTensorのプロパティ(形状、データ型)を保持したまま別のTensorを作成できる

# x_dataのプロパティをそのまま利用して要素が1のTensorを作成
x_ones = torch.ones_like(x_data)
print(f"Ones Tensor \n {x_ones} \n")

# 同様に今度は要素が0 ~ 1の乱数のTensorを作成
x_rand = torch.rand_like(x_data, dtype=torch.float)
print(f"Rand Tensor \n {x_rand} \n")
"""
Ones Tensor 
 tensor([[1, 1],
        [1, 1]]) 

Rand Tensor 
 tensor([[0.4756, 0.4901],
        [0.4540, 0.7237]]) 
"""

4. ランダム値や定数のTensorを作成

# shapeでTensorのサイズを指定
shape = (2, 3,)
# 乱数, 1, 0のTensorをそれぞれ生成
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")

"""
Random Tensor: 
 tensor([[0.0850, 0.1130, 0.8770],
        [0.3623, 0.8721, 0.3704]]) 

Ones Tensor: 
 tensor([[1., 1., 1.],
        [1., 1., 1.]]) 

Zeros Tensor: 
 tensor([[0., 0., 0.],
        [0., 0., 0.]])
"""

現状、そこまで難しいことはないですね。リストやndarrayからTensorへ変換したり、乱数で初期化されたTensorを生成する方法が理解できました。

テンソルの属性変数

Tensorには属性変数としてそのTensorの形状、データ型、保存されているデバイス等のプロパティが保存されているらしい。

tensor = torch.rand(3, 4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

"""
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu
"""

テンソルの操作

Tensorにはたくさんの演算が用意されていて、それらは全てGPU上で実行が可能!(デフォルトはCPU上で動く)
そのため、GPUが利用可能ならTensorをGPU上へ動かす必要がある(GPUを使うならの話)

# GPU(CUDA)が利用可能なら、TensorをGPUに移動させる
if torch.cuda.is_available():
    tensor = tensor.to('cuda')

1. sliceやindexの使用

tensor = torch.ones(4, 4)
print('First row: ', tensor[0])
print('First column: ', tensor[:, 0])
print('Last column: ', tensor[..., -1])
tensor[:, 1] = 0
print(tensor)
"""
First row:  tensor([1., 1., 1., 1.])
First column:  tensor([1., 1., 1., 1.])
Last column:  tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
"""

NumPyに似ていてこの辺も扱いやすくていいですね。

2. テンソルの結合
torch.catを使用することで同じ次元のテンソルを結合できます。これの例だけではよくわからなかったので調べつつ自分で実装してみました。(参考

input1 = torch.rand(2, 3, 4)
input2 = torch.rand(2, 3, 4)
input3 = torch.rand(5, 3, 4)

output = torch.cat([input1, input2, input3], dim=0)
print(output.shape)

"""
torch.Size([9, 3, 4])
"""

なるほど、dim=0で結合するなら、dim=1, 2の次元はもちろん同じじゃないといけないことがわかりました。
日本語verに書かれていないので補足としてtorch.stackとの違いも調査してみました。

  • torch.cat:存在する次元を指定して結合する
  • torch.stack:新たに次元を追加して結合する((2, 3)同士をdim=2でstackして(2, 3, 2)にできたりする!)

3. 算術演算

  • 行列の積(Matmul)
# 3種類の書き方がある
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
y3 = torch.rand_like(tensor)
torch.matmul(tensor, tensor.T, out=y3)
print(y1)
print(y2)
print(y3)

"""
出力は同じ
tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])
tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])
tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])
"""
  • 行列の要素積
z1 = tensor * tensor
z2 = tensor.mul(tensor)

z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)

print(z1)
print(z2)
print(z3)

"""
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])
"""
  • 1要素のテンソル(テンソルの全要素を足した結果)をpythonの数値型に変換
agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))

"""
12.0 <class 'float'>
"""
  • インプレース操作
    演算の結果をオペランドに格納する演算のことを言うらしい。(よくわからない)
print(tensor, "\n")
tensor.add_(5)
print(tensor)

"""
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]]) 

tensor([[6., 5., 6., 6.],
        [6., 5., 6., 6.],
        [6., 5., 6., 6.],
        [6., 5., 6., 6.]])
"""

変数tensorをそのまま更新することでメモリが節約できるから良いらしい。しかし、微分計算などでは問題になるため、そのようなシチュエーションでは使わないことが推奨されています。

NumPyへの変換

NumpyからTensorへの変換と同じように、CPU上のTensorはNumPyに変換することができる。

t = torch.ones(5)
print(f"t : {t}, type: {type(t)}")
n = t.numpy()
print(f"n : {n}, type: {type(n)}")

"""
t : tensor([1., 1., 1., 1., 1.]), type: <class 'torch.Tensor'>
n : [1. 1. 1. 1. 1.], type: <class 'numpy.ndarray'>
"""

さらに、Numpyに加えた変化はTensorにも反映されるんですって。(なんで??)

np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")

"""
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
"""

ここまでお読みいただきありがとうございました。TensorとNumPyの互換性が高く、機械学習を勉強した際のことがここでも活用できそうでワクワクしています。

また次の章も同じように続けていきますのでよろしくお願いします!!

Discussion