PyTorchのチュートリアルのメモ

# 目的
[PyTorch](https://pytorch.org/)のTutorialを簡単にまとめてみます。
環境は[Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)を使用しています。


## WHAT IS PYTORCH?
参考ドキュメントはこちらです。
[What is PyTorch? — PyTorch Tutorials 1.4.0 documentation](https://pytorch.org/tutorials/beginner/blitz/tensor_tutorial.html#sphx-glr-beginner-blitz-tensor-tutorial-py)
以下引用
> A replacement for NumPy to use the power of GPUs
(GPUのパワーを使用するNumpyの代わりになります。)
> a deep learning research platform that provides maximum flexibility and speed
(最大限の柔軟性と速度を提供する深層学習研究のプラットフォームです。)

## Getting Started

### ライブラリのimport

```
import torch
import numpy as np
```

### Tensors
TensorはNumpyのndarraysに似ているが、TensorはGPUを使用することで高速化が可能です。
初期化が行われていない5×3の行列を作成します。

```python:空の5×3行列を作成
x = torch.empty(5, 3)
print(x)
```
初期化されていない行列が作成されると、その時点でメモリにあった値が初期値として表示されます。  

```bash:出力結果
tensor([[ 2.2400e+04,  0.0000e+00,  4.4842e-44],
        [ 0.0000e+00,         nan,  5.6052e-45],
        [ 4.2729e-05,  1.0502e-05,  6.8998e-07],
        [ 1.0990e-05,  3.3423e+21,  3.3089e-09],
        [ 1.0742e-05,  1.0386e+21, -1.6165e+19]])
```

ランダムに初期化された行列を作成します。

```python:ランダムに初期化された行列を作成
x = torch.rand(5, 3)
print(x)
```

```bash:出力結果
tensor([[0.4542, 0.2150, 0.2701],
        [0.9506, 0.9751, 0.5338],
        [0.1317, 0.5831, 0.2523],
        [0.8549, 0.4715, 0.2544],
        [0.5056, 0.9718, 0.1719]])
```

0で満たされたdtype longの行列を作成します。

```python:0で満たされたdtypeがlongの行列を作成
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
```

```bash:出力結果
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])
```

全ての要素が1の行列を作成します。 同じサイズの異なるdtypeで上書きを行います。

```python:全ての要素が1の行列を作成して、同じサイズの異なるdtypeで上書き
x = x.new_ones(5, 3, dtype=torch.double)
print(x)

x = torch.randn_like(x, dtype=torch.float)
print(x)
```

```bash:出力結果
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
tensor([[ 0.5442, -0.9881,  0.2075],
        [ 1.2789, -2.6781,  0.0876],
        [ 1.1751, -0.0895, -1.2029],
        [ 0.1122, -0.0397,  1.4177],
        [-0.5388,  1.6711, -0.7571]])
```

torch.Sizeはタプルなので、タプル操作は全てサポートしています。

```python:サイズの表示
print(x.size())
```

```bash:出力結果
torch.Size([5, 3])
```

### Operations

複数の加算操作(足し算)を試してみます。

加算対象のTensorを作成します。

```python:対象のTensorを作成
x = torch.rand(5, 3)
y = torch.rand(5, 3)

print(f"x : {x}")
print(f"y : {y}")
```

```bash:出力結果
x : tensor([[0.6193, 0.4215, 0.4377],
        [0.6347, 0.4114, 0.5780],
        [0.5628, 0.0948, 0.5263],
        [0.7267, 0.8663, 0.9665],
        [0.4584, 0.2276, 0.2786]])
y : tensor([[0.2985, 0.3849, 0.8744],
        [0.4740, 0.4313, 0.2473],
        [0.5549, 0.7964, 0.3837],
        [0.0819, 0.8110, 0.6297],
        [0.3824, 0.5662, 0.3157]])
```

Tensor同士を足してあげます。

```python:変数同士の足し算
x + y
```

```bash:出力結果
tensor([[0.9178, 0.8063, 1.3121],
        [1.1087, 0.8427, 0.8253],
        [1.1177, 0.8912, 0.9100],
        [0.8086, 1.6773, 1.5963],
        [0.8408, 0.7938, 0.5943]])
```

addで対象を２つ選択します。

```python:addの使用
torch.add(x, y)
```

```bash:出力結果
tensor([[0.9178, 0.8063, 1.3121],
        [1.1087, 0.8427, 0.8253],
        [1.1177, 0.8912, 0.9100],
        [0.8086, 1.6773, 1.5963],
        [0.8408, 0.7938, 0.5943]])
```

空の行列を作成してaddの結果を格納することもできます。

```python:addと空の行列を使用
result = torch.empty(5, 3)
torch.add(x, y, out=result)
```

```bash:出力結果
tensor([[0.9178, 0.8063, 1.3121],
        [1.1087, 0.8427, 0.8253],
        [1.1177, 0.8912, 0.9100],
        [0.8086, 1.6773, 1.5963],
        [0.8408, 0.7938, 0.5943]])
```

Tensorをその場で変更する操作はすべて、_で固定されます。
つまり yの値が書き換わります。

```python:yが書き換わる例
y.add_(x)
```

```bash:出力結果
tensor([[0.9178, 0.8063, 1.3121],
        [1.1087, 0.8427, 0.8253],
        [1.1177, 0.8912, 0.9100],
        [0.8086, 1.6773, 1.5963],
        [0.8408, 0.7938, 0.5943]])
```

Numpyと同じようにインデックスで操作できます。 xのTensorの1列目の値を取得しています。

```python:スライス操作
x[:, 1]
```

```bash:出力結果
tensor([0.4215, 0.4114, 0.0948, 0.8663, 0.2276])
```

Tensorのサイズを変更するにはtorch.viewを使用します。
引数のサイズが対象のTensorと同じである必要があります。
引数が -1の場合は他の次元から推測してくれます。

```python:サイズの変更
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)
print(f'x size : {x.size()}')
print(f'y size : {y.size()}')
print(f'z size : {z.size()}')
```

```bash:出力結果
x size : torch.Size([4, 4])
y size : torch.Size([16])
z size : torch.Size([2, 8])
```

Tensorの要素が１つの場合、item()を使用してPythonの数値として値を取得します。

```python
x = torch.randn(1)
print(x)
print(x.item())
print(type(x.item()))
```

```bash:出力結果
tensor([-0.0423])
-0.042286407202482224
<class 'float'>
```

他にも転置、インデックス付け、スライシング、数学演算、線形代数、乱数などを含む100を超える演算があります。
[torch — PyTorch master documentation](https://pytorch.org/docs/stable/torch.html)

### NumPy Bridge
NumpyとTensorの変換を行います。
NumpyとTensorはメモリの位置を共有します。（TensorがCPU上にある場合）
→ 変更が共有されます。

#### Converting a Torch Tensor to a NumPy Array
Tensor から Numpy に変換します。

```python:Tensorの作成
a = torch.ones(5)
print(a)
print(type(a))
```

```bash:出力結果
tensor([1., 1., 1., 1., 1.])
<class 'torch.Tensor'>
```

```python:TensorからNumpyに変換
b = a.numpy()
print(b)
print(type(b))
```

```bash:出力結果
[1. 1. 1. 1. 1.]
<class 'numpy.ndarray'>
```

```python:cpu上にあるので値が同時に書き換わる
a.add_(1)
print(a)
print(b)
```

```bash:出力結果
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]
```

#### Converting NumPy Array to Torch Tensor

Numpy から Temsor に変換します。

```python
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)
```

```bash:出力結果
[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
```

### CUDA Tensors

.toメソッドを使用してTensorを任意のデバイスに移動できます。

```python:Tensorを任意のデバイスに移動
if torch.cuda.is_available():
  device = torch.device("cuda")  
  y = torch.ones_like(x, device=device)
  x = x.to(device)
  z = x + y
  print(z)
  print(z.to("cpu", torch.double))
```

```bash:出力結果
tensor([0.9577], device='cuda:0')
tensor([0.9577], dtype=torch.float64)
```

## 終わりに
簡単にチュートリアルを出力しながら行なってみました。
チュートリアルは本当に基礎の部分しか触れないようです。
演算も100以上あるそうです。（時間がある時に目を通しておこう。。。）
PyTorchを使用している実感があまり無いですが、もう少し先のチュートリアルもやって行けたらいいなと思っています。
最後まで目を通していただきありがとうございました！！