どうしてこの記事を書いたの?
PyTorchについて勉強しようと思い、この本を買ってみました。
いくつか見比べてみて、初学者向けのものを選んだのですが、、、よくわからん!!!
となりました。
出力を書いていないから何が出力されて、どういう観点を確認すればわからないんです!!
なので、おそらく言わんとしていることはこうだろうという補足をまとめてみました。
実行環境はGoogle Colaboratoryとしています。
下準備として、PyTorchをインストールします。
import torch
PyTorchではGPUを使用します。GPUが使用可能かどうか確認してみましょう。
# GPUが使えているかどうか確認する
print(torch.cuda.is_available())
True
Tensorって何?
書籍によるとTensorとは、
- 多次元配列を扱うためのデータ構造である
- Numpyのndarrayとほぼ同様のAPIを有する
- GPU上での計算もサポートする
だそうです。
要するにPyTorchの多次元配列
です。
書籍ではごたごたとデータ型云々の説明とサンプルコードがありましたが、定義だけして全く中身を確認していません。
なので、データの確認をしていきます。
# 入れ子のlistを渡して作成する
t = torch.tensor([[1, 2], [3, 4.]])
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
dtype: torch.float32
device: cpu
tensor([[1., 2.],
[3., 4.]])
dtype
はデータ型、device
はTensorがCPU / GPUどちらにあるかです。
GPU上にTensorを作ってみます。
t = torch.tensor([[1, 2], [2, 4.]], device="cuda:0")
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
dtype: torch.float32
device: cuda:0
tensor([[1., 2.],
[2., 4.]], device='cuda:0')
torch.cuda.FloatTensor
でもGPU上にTensorを作ることができるようです。
t = torch.cuda.FloatTensor([[1, 2], [2, 4.]])
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
dtype: torch.float32
device: cuda:0
tensor([[1., 2.],
[2., 4.]], device='cuda:0')
ちなみに、cudaはWikipediaによると、
CUDA(Compute Unified Device Architecture:クーダ)とは、NVIDIAが開発・提供している、GPU向けの汎用並列コンピューティングプラットフォーム(並列コンピューティングアーキテクチャ)およびプログラミングモデルである
だそうです。
今度は、データ型を変えてみます。
# 64bitのデータ型を指定する
t = torch.tensor([[1, 2], [2, 4.]], dtype=torch.float64)
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
dtype: torch.float64
device: cpu
tensor([[1., 2.],
[2., 4.]], dtype=torch.float64)
64bitの符号付整数はtorch.LongTensor
でも定義できます。
t = torch.LongTensor([[1, 2], [2, 4.]])
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
dtype: torch.int64
device: cpu
tensor([[1, 2],
[2, 4]])
データ型についてはこちらのドキュメントでまとまっていました。
https://pytorch.org/docs/stable/tensors.html
CPU上に作成したTensorをGPUへ転送してみます。
t = torch.zeros(100, 10).to("cuda:0")
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t[:2] # Tensorでもスライスが利用できます
dtype: torch.float32
device: cuda:0
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], device='cuda:0')
Tensorのshapeを確認する場合は、shape関数ではなく、size関数を使用します
。
この点はNumPyと違います。
# shapeはsizeで取得する
# t.shape() # コンパイルエラー
t.size()
torch.Size([100, 10])
TensorをNumpyのndarrayに変換するにはnumpy関数を使用します。
t = torch.tensor([[1, 2], [3, 4.]])
x = t.numpy()
print("type: {}".format(type(x)))
x
type: <class 'numpy.ndarray'>
array([[1., 2.],
[3., 4.]], dtype=float32)
GPU上のTensorはCPUへ変換する必要があります。
t = torch.cuda.FloatTensor([[1, 2], [3, 4.]])
x = t.to("cpu").numpy()
print("type: {}".format(type(x)))
x
type: <class 'numpy.ndarray'>
array([[1., 2.],
[3., 4.]], dtype=float32)
Tensorの変換
Tensorを変換する方法のうち、紛らわしいものをやってみます。
Numpyで言うところのreshapeはview関数
で実行します。
# 2 * 2を4 * 1に変更する
# viewはndarrayのreshapeと同様
x1 = torch.tensor([[1, 2], [3, 4.]])
x1.view(4, 1)
tensor([[1.],
[2.],
[3.],
[4.]])
転置の書き方はいくつかあります。
x2 = torch.tensor([[10, 20, 30], [40, 50, 60.]])
x2.T
tensor([[10., 40.],
[20., 50.],
[30., 60.]])
x2.t()
tensor([[10., 40.],
[20., 50.],
[30., 60.]])
transposeも転置を行うことができますが、転置以外に画像データのデータ形式をHWC(縦、横、色)からCHW(色、横、縦)に並べ替える際にも利用できるそうです。
hwc_img_data = torch.rand(1, 5, 4, 3)
hwc_img_data
tensor([[[[0.9248, 0.7545, 0.5603],
[0.5339, 0.6627, 0.7652],
[0.7082, 0.5146, 0.9273],
[0.8437, 0.7064, 0.1053]],
[[0.2080, 0.8018, 0.6833],
[0.5892, 0.9264, 0.9315],
[0.0872, 0.1898, 0.5745],
[0.2192, 0.1187, 0.7537]],
[[0.9680, 0.9239, 0.8698],
[0.2339, 0.9918, 0.3446],
[0.6669, 0.4148, 0.2037],
[0.1055, 0.0353, 0.3679]],
[[0.7079, 0.4069, 0.1181],
[0.1983, 0.0452, 0.5788],
[0.6378, 0.7050, 0.1148],
[0.3960, 0.1924, 0.2714]],
[[0.3127, 0.1320, 0.7232],
[0.3484, 0.7617, 0.4725],
[0.4863, 0.9178, 0.3092],
[0.6279, 0.4259, 0.3828]]]])
自分は1 * 5 * 4 * 3から 1 * 4 * 5 * 3に変わる(5と4が入れ替わる)と考えると何となく理解できました。
# 1 * 5 * 4 * 3から 1 * 4 * 5 * 3に変わる
hwc_img_data.transpose(1, 2)
tensor([[[[0.9248, 0.7545, 0.5603],
[0.2080, 0.8018, 0.6833],
[0.9680, 0.9239, 0.8698],
[0.7079, 0.4069, 0.1181],
[0.3127, 0.1320, 0.7232]],
[[0.5339, 0.6627, 0.7652],
[0.5892, 0.9264, 0.9315],
[0.2339, 0.9918, 0.3446],
[0.1983, 0.0452, 0.5788],
[0.3484, 0.7617, 0.4725]],
[[0.7082, 0.5146, 0.9273],
[0.0872, 0.1898, 0.5745],
[0.6669, 0.4148, 0.2037],
[0.6378, 0.7050, 0.1148],
[0.4863, 0.9178, 0.3092]],
[[0.8437, 0.7064, 0.1053],
[0.2192, 0.1187, 0.7537],
[0.1055, 0.0353, 0.3679],
[0.3960, 0.1924, 0.2714],
[0.6279, 0.4259, 0.3828]]]])
# さらに1 * 4 * 5 * 3 から1 * 3 * 4 * 5に変わる
# これでhwcからcwhへ変換できる
hwc_img_data.transpose(1, 2).transpose(1, 3)
tensor([[[[0.9248, 0.5339, 0.7082, 0.8437],
[0.2080, 0.5892, 0.0872, 0.2192],
[0.9680, 0.2339, 0.6669, 0.1055],
[0.7079, 0.1983, 0.6378, 0.3960],
[0.3127, 0.3484, 0.4863, 0.6279]],
[[0.7545, 0.6627, 0.5146, 0.7064],
[0.8018, 0.9264, 0.1898, 0.1187],
[0.9239, 0.9918, 0.4148, 0.0353],
[0.4069, 0.0452, 0.7050, 0.1924],
[0.1320, 0.7617, 0.9178, 0.4259]],
[[0.5603, 0.7652, 0.9273, 0.1053],
[0.6833, 0.9315, 0.5745, 0.7537],
[0.8698, 0.3446, 0.2037, 0.3679],
[0.1181, 0.5788, 0.1148, 0.2714],
[0.7232, 0.4725, 0.3092, 0.3828]]]])
Tensorと自動微分
requires_grad属性をTrueにすると自動微分を行います。
書籍ではどこで何が出現するのか詳しく書いていないので、補足をしています。
x = torch.randn(100, 3)
a = torch.tensor([1, 2, 3.], requires_grad=True)
y = torch.mv(x, a)
print(y.grad_fn) # 勾配を計算するための計算グラフが格納されていることを確認する
o = y.sum()
# 勾配を獲得する
# backwardを実行する変数はスカラーである必要がある
o.backward()
# 自動微分が実行されて、a.gradに勾配が獲得される
print(a.grad)
a.grad != x.sum(0)
<MvBackward object at 0x7f9c7998e3c8>
tensor([ 2.5016, 6.3925, -6.3674])
tensor([False, False, False])