PyTorchの書籍が分かりにくかったので補足してみた

# どうしてこの記事を書いたの？
PyTorchについて勉強しようと思い、この本を買ってみました。  

* [現場で使える！PyTorch開発入門 深層学習モデルの作成とアプリケーションへの実装](https://www.amazon.co.jp/dp/479815718X)

いくつか見比べてみて、初学者向けのものを選んだのですが、、、`よくわからん！！！`となりました。
_**出力を書いていないから何が出力されて、どういう観点を確認すればわからないんです！！**_  
  
なので、おそらく言わんとしていることはこうだろうという**補足**をまとめてみました。
実行環境は[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)としています。
  
下準備として、PyTorchをインストールします。  

```python:IN
import torch
```

PyTorchではGPUを使用します。GPUが使用可能かどうか確認してみましょう。

```python:IN
# GPUが使えているかどうか確認する
print(torch.cuda.is_available())
```

```:OUT
True
```

# Tensorって何？
書籍によるとTensorとは、

- 多次元配列を扱うためのデータ構造である
- Numpyのndarrayとほぼ同様のAPIを有する
- GPU上での計算もサポートする

だそうです。
  
要するにPyTorchの`多次元配列`です。
書籍ではごたごたとデータ型云々の説明とサンプルコードがありましたが、定義だけして**全く中身を確認していません**。
なので、**データの確認をしていきます**。

```python:IN
# 入れ子のlistを渡して作成する
t = torch.tensor([[1, 2], [3, 4.]])
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
```

```:OUT
dtype: torch.float32
device: cpu
tensor([[1., 2.],
        [3., 4.]])
```

`dtype`はデータ型、`device`はTensorがCPU / GPUどちらにあるかです。
GPU上にTensorを作ってみます。

```python:IN
t = torch.tensor([[1, 2], [2, 4.]], device="cuda:0")
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
```

```:OUT
dtype: torch.float32
device: cuda:0
tensor([[1., 2.],
        [2., 4.]], device='cuda:0')
```

`torch.cuda.FloatTensor`でもGPU上にTensorを作ることができるようです。

```python:IN
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](https://ja.wikipedia.org/wiki/CUDA)によると、
> CUDA（Compute Unified Device Architecture：クーダ）とは、NVIDIAが開発・提供している、GPU向けの汎用並列コンピューティングプラットフォーム（並列コンピューティングアーキテクチャ）およびプログラミングモデルである

だそうです。

今度は、データ型を変えてみます。

```python:IN
# 64bitのデータ型を指定する
t = torch.tensor([[1, 2], [2, 4.]], dtype=torch.float64)
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
```

```:OUT
dtype: torch.float64
device: cpu
tensor([[1., 2.],
        [2., 4.]], dtype=torch.float64)
```

64bitの符号付整数は`torch.LongTensor`でも定義できます。

```python:IN
t = torch.LongTensor([[1, 2], [2, 4.]])
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t
```

```:OUT
dtype: torch.int64
device: cpu
tensor([[1, 2],
        [2, 4]])
```

データ型についてはこちらのドキュメントでまとまっていました。
https://pytorch.org/docs/stable/tensors.html

CPU上に作成したTensorをGPUへ転送してみます。

```python:IN
t = torch.zeros(100, 10).to("cuda:0")
print("dtype: {}\ndevice: {}".format(t.dtype, t.device))
t[:2] # Tensorでもスライスが利用できます
```

```:OUT
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と違います。

```python:IN
# shapeはsizeで取得する
# t.shape() # コンパイルエラー
t.size()
```

```:OUT
torch.Size([100, 10])
```

TensorをNumpyのndarrayに変換するにはnumpy関数を使用します。

```python:IN
t = torch.tensor([[1, 2], [3, 4.]])
x = t.numpy()
print("type: {}".format(type(x)))
x
```

```:OUT
type: <class 'numpy.ndarray'> 
array([[1., 2.],
       [3., 4.]], dtype=float32)
```

GPU上のTensorはCPUへ変換する必要があります。

```python:IN
t = torch.cuda.FloatTensor([[1, 2], [3, 4.]])
x = t.to("cpu").numpy()
print("type: {}".format(type(x)))
x
```

```:OUT
type: <class 'numpy.ndarray'>
array([[1., 2.],
       [3., 4.]], dtype=float32)
```

# Tensorの変換
Tensorを変換する方法のうち、紛らわしいものをやってみます。
  
Numpyで言うところのreshapeは`view関数`で実行します。

```python:IN
# 2 * 2を4 * 1に変更する
# viewはndarrayのreshapeと同様
x1 = torch.tensor([[1, 2], [3, 4.]])
x1.view(4, 1)
```

```:OUT
tensor([[1.],
        [2.],
        [3.],
        [4.]])
```

転置の書き方はいくつかあります。

```python:IN
x2 = torch.tensor([[10, 20, 30], [40, 50, 60.]])
x2.T
```

```:OUT
tensor([[10., 40.],
        [20., 50.],
        [30., 60.]])
```

```python:IN
x2.t()
```

```:OUT
tensor([[10., 40.],
        [20., 50.],
        [30., 60.]])
```

transposeも転置を行うことができますが、転置以外に画像データのデータ形式をHWC（縦、横、色）からCHW(色、横、縦）に並べ替える際にも利用できるそうです。

```python:IN
hwc_img_data = torch.rand(1, 5, 4, 3)
hwc_img_data
```

```:OUT
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が入れ替わる）と考えると何となく理解できました。


```python:IN
# 1 * 5 * 4 * 3から 1 * 4 * 5 * 3に変わる
hwc_img_data.transpose(1, 2)
```

```:OUT
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]]]])
```

```python:IN
# さらに1 * 4 * 5 * 3 から1 * 3 * 4 * 5に変わる
# これでhwcからcwhへ変換できる
hwc_img_data.transpose(1, 2).transpose(1, 3)
```

```:OUT
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にすると自動微分を行います。
書籍ではどこで何が出現するのか詳しく書いていないので、補足をしています。

```python:IN
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)
```

```:OUT
<MvBackward object at 0x7f9c7998e3c8>
tensor([ 2.5016,  6.3925, -6.3674])
tensor([False, False, False])
```