pytorch 0.4の変更点

もう先週になりますがpytorch0.4が出ました。
自分がpytorchを使い始めたのは0.3以降なので初めてのメジャー（？）バージョンアップになります。
1.0以下のバージョンアップはやはり安定しないもので、大きな変更がいくつかありますね。

# 主な変更点
[Release notes](https://github.com/pytorch/pytorch/releases)

- TensorとVariableの統合
- ゼロ次元テンソル
- dtypesとtesnorsの導入
- advanced indexingの完全サポート
- FFT
- 計算グラフのチェックポイント機能
- ボトルネック分析機能
- 確率分布の拡充
- ONNXでRNNのサポート
- winodes対応

# TensorとVariableの統合
0.4以降は何回も`Variable(x)`なんて書く必要がなくなりました！
`Tensor`に計算グラフを保持する機能が追加され、backwardすることが出来ます。

```py
>>> x = torch.DoubleTensor([1,2,3])
>>> x
tensor([ 1.,  2.,  3.], dtype=torch.float64)
>>> x.requires_grad
False
```

デフォルトだと`requires_grad=False`となっており計算グラフを保持してくれないので、有効にするためには`my_tensor.requires_grad_(requires_grad=True)`を呼び出すか、有効な状態で作成する必要があります。

```py
>>> x.requires_grad_()
tensor([ 1.,  2.,  3.], dtype=torch.float64)
>>> x.requires_grad
True
>>> y = torch.ones(1, requires_grad=True)
>>> y.requires_grad
True
```

いままで`Variable`から`Tensor`を取り出すために`.data`を使っていたと思いますが、0.4では`.data`は`requires_grad=False`となった`Tensor`を返すようになります。

```py
>>> yd = y.data
>>> yd.requires_grad
False
```

ただ、`.data`で計算グラフの途中のノードを参照して変更を行った場合逆伝播時に問題が起きるらしく、葉ノードを生成する`.detach()`の使用が推奨されているようです。（ここらへん良く理解できいなので後で調べます。）

# ゼロ次元テンソル
いままではスカラーを表すテンソルは存在せず、長さ１のベクトルを代用していました。
なのでスカラ値を示すテンソルが出来ました。以上。

```py
>>> a = torch.tensor(1)
>>> a
tensor(1)
>>> a.size()
torch.Size([])
```

これに伴いいくつかの関数の返り値が変わります。

```py
>>> b = torch.arange(1,5)
>>> b
tensor([ 1.,  2.,  3.,  4.])
>>> b.sum()
tensor(10.)
```

ゼロ次元テンソルからpythonの値を取り出す場合、`.item()`を使います。
`loss.data[0]`と書く必要がなくなりました。
というより、ゼロ次元なのでindexingすると警告が出ます。0.5でエラーになるようです。

```py
>>> loss.item()
1
>>> loss.data[0]
__main__:1: UserWarning: invalid index of a 0-dim tensor. This will be an error in PyTorch 0.5. Use tensor.item() to convert a 0-dim tensor to a Python number
tensor(1)
```

# dtypes等の導入
Numpyに似たテンソルの作成方法が出来るようになりました。
いままで`FloatTensor`などテンソルの作成にはクラスごとに使い分けていたのが、`tensor`の引数を変えることで出来るようになります。

```py
>>> device = torch.device('cpu')
>>> torch.tensor([1,2,3], dtype=torch.float32, device=device)
tensor([ 1.,  2.,  3.])
>>> torch.randn(3, dtype=torch.float32, device=device)
tensor([ 1.6814, -0.6660,  0.7717])
```

`dtype`、`device`、`layout`をそれぞれ指定出来ます。

- dtypeは型を表します
    - `torch.float32`や`torch.int32`など
- deviceは作成する場所を表します
    - `cpu`や`cuda:0`などで指定します。`cuda`と指定した場合デフォルトのGPUを使用します
- layoutはテンソルのレイアウト（？）を表します
    - 現状密なテンソル(torch.strided)と疎なテンソル(torch.sparse_coo)が存在します

また`.cuda`の代わりに`.to(device)`というのが導入されました。
CPUとGPUの両方で使えるコードを以下のように作れます。

```python
# いままで
model = Model()
if use_cuda:
    model = model.cuda()

# 0.4以降
device = torch.device('cuda:0')  # or 'cpu'
model = model.to(device)
```

deviceを共有することで、条件分岐などを無くスッキリ書けます。

# advanced indexing
Numpyのadvanced indexingが使えるようになりました。
ブロードキャストなどです。
numpy芸は出来ないので、なにが出来るようになったのか実感がないですが、便利なのでしょう。

# 計算グラフのチェックポイント
ニューラルネットワークの計算時には逆伝播のために計算結果をすべて保持する必要がありメモリを大変食います。
そこで、一度計算結果を捨てて逆伝播の時に必要となったら再計算することで使用メモリを減らすチェックポイントを作れるようになりました。
再計算するため訓練速度は落ちますが、重いのは大抵逆伝播の処理なので訓練時間が数倍になったりはしないと思います。

# プロファイリングのためのツールの追加
ボトルネックを見つけるための`torch.utils.bottleneck`が追加されたようです。
0.3の時点でGPU usageを見るものはあった気がしますがどう変更されたのか気になりますね。
[bottleneck docs](https://pytorch.org/docs/master/bottleneck.html)

# その他細かい変更

## einsumの導入
tensorflowで使えたenisumが使えるようになりました。

```py
>>> w = torch.tensor([[1,2], [2,3]])
>>> v = torch.tensor([3,4])
>>> torch.einsum('ik,k->i', (w, v))
tensor([ 11,  18])
```

速度的にどうなのか分かりませんが、複雑な演算をしたい時に便利です。