PyTorchで絶対nanを出したいマン

皆さん、いかがお過ごしでしょうか。

さて、今回の記事は以下のような方が対象です。

- nanを出したくてしょうがない
- nanと結婚したい
- nan対策のwarningとか出すとか無粋すぎると思っている

それではいきましょう。

# forward計算中にnanを出す

## 定義域を外れた値を代入する

```
>>> a = torch.tensor([-2],dtype=torch.float, requires_grad=True)
>>> torch.log(a)
tensor([nan], grad_fn=<LogBackward>)
```

やったぜ。

これは基本ですね。
logの定義域が $ x > 0 $なためですね。

なぜか、以前のPyTorchのチュートリアルでは全てnanを出すという荒技を披露しています。
[https://pytorch.org/docs/1.3.1/torch.html?highlight=log#torch.log](https://pytorch.org/docs/1.3.1/torch.html?highlight=log#torch.log)

その他、log1p($x>-1$)やsqrt($x \geq 0$, ただし、$x=0$の時backward時にnanが出る)、arcsin($-1 \leq x \leq 1$)やarccos($-1 \leq x \leq 1$)でも出てきます。

```
>>> a = torch.tensor([2],dtype=torch.float, requires_grad=True)
>>> y = torch.acos(a)
>>> print(y)
tensor([nan], grad_fn=<AcosBackward>)
>>> y = torch.asin(a)
>>> print(y)
tensor([nan], grad_fn=<AsinBackward>)
```

ちなみにnumpyならwarningが出ます。

```
>>> import numpy as np
>>> np.arcsin(2)
__main__:1: RuntimeWarning: invalid value encountered in arcsin
nan
>>> np.log(-2)
__main__:1: RuntimeWarning: invalid value encountered in log
nan
```

# backward時にnanを出す

## sqrtの中身が0

```
>>> x = torch.tensor([1,2,0], dtype=torch.float, requires_grad=True)
>>> y = torch.sqrt(x)
>>> loss = torch.nn.functional.mse_loss(x,y)
>>> loss.backward()
>>> print(x.grad)
tensor([0.0000, 0.2525,    nan])
```
やったわ。

### 理論的な何か

これはsqrtを用いた場合,

```math
y = \sqrt{x} \\
\frac{dy}{dx} = \frac{1} {2\sqrt x} 
```

になり,x=0を代入することでinfが出ることになります。

infになにか計算するとnanが出るというわけですね。

### arctanの中身が0

0シリーズ。第2弾。
厳密にいうと0/0になった時です。

```
>>> import torch
>>> a = torch.tensor([0],dtype=torch.float, requires_grad=True)
>>> b = torch.tensor([0],dtype=torch.float, requires_grad=True)
>>> y = torch.atan2(a,b)
>>> print(y)
tensor([0.], grad_fn=<Atan2Backward>)
>>> y.backward()
>>> print(a.grad)
tensor([nan])
>>> print(b.grad)
tensor([nan])
```

やりましたわ。

### 理論的な何か

arctanの微分値が

```math
y = \arctan{x} \\
\frac{dy}{dx} = \frac{1} {x^2 + 1} 
```

なのですが、x=0/0が入ることで計算結果自体がnanになります。

### expの値が大きい

```
>>> a = torch.rand((100), requires_grad=True) 
>>> a.data += 1000
>>> c = torch.rand((100))
>>> a
tensor([1000.2039, 1000.5928, 1000.9624, 1000.3787, 1000.5309, 1000.2106,
        1000.5556, 1000.3329, 1000.4319, 1000.1736, 1000.3649, 1000.2134,
        1000.8217, 1000.2623, 1000.6758, 1000.7391, 1000.1855, 1000.2891,
        1000.0947, 1000.9935, 1000.3480, 1000.4997, 1000.6566, 1000.0687,
        1000.1025, 1000.7646, 1000.6289, 1000.0661, 1000.8058, 1000.4211,
        1000.7621, 1000.2367, 1000.4923, 1000.9172, 1000.5065, 1000.5551,
        1000.0206, 1000.5237, 1000.7859, 1000.2126, 1000.6104, 1000.7820,
        1000.6644, 1000.1710, 1000.6781, 1000.3405, 1000.2715, 1000.8885,
        1000.6795, 1000.3990, 1000.3657, 1000.2349, 1000.3822, 1000.6222,
        1000.3250, 1000.7546, 1000.9409, 1000.2061, 1000.9879, 1000.1796,
        1000.2593, 1000.7741, 1000.4854, 1000.0928, 1000.5674, 1000.2328,
        1000.9003, 1000.2019, 1000.1033, 1000.2095, 1000.2926, 1000.9736,
        1000.8083, 1000.9316, 1000.7747, 1000.6843, 1000.7141, 1000.4777,
        1000.0200, 1000.0708, 1000.8871, 1000.6099, 1000.4662, 1000.9747,
        1000.2898, 1000.8978, 1000.1368, 1000.2803, 1000.3809, 1000.9002,
        1000.6716, 1000.3039, 1000.6280, 1000.7723, 1000.8350, 1000.4714,
        1000.5219, 1000.2311, 1000.8187, 1000.5468], requires_grad=True)
>>> d = c / torch.exp(a)
>>> d.sum().backward()
>>> a.grad
tensor([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
        nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
        nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
        nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
        nan, nan, nan, nan])

```
やりましたわぜ。

### 理論的な何か

expに大きな値を入れるとinfになります。
その状態で割り算(`d = c / torch.exp(a)`)をすると0になります。
それをbackward()をするとnanになるというわけですね。

softmax関数を自前で実装すると結構起こりやすい現象ですね。

## ctc_lossの入力系列長より出力系列長の方が大きい時

[torch.nn.functional.ctc_loss](https://pytorch.org/docs/stable/nn.functional.html?highlight=ctc_loss#torch.nn.functional.ctc_loss)はアルゴリズム的に入力系列長よりも出力系列長が長いものは受け付けていません。

実際に入れるとPyTorchではinfが出てきてそれ以降のパラメータはnanになります。

```
import torch
import torch.nn as nn
import torch.nn.functional as F

# 入力 (長さ = 5 x バッチ x クラス数)
log_probs = torch.randn(5, 1, 20).log_softmax(2).detach().requires_grad_()
# 出力 (長さ = 30)
targets = torch.randint(1, 20, (1, 30), dtype=torch.long)
input_lengths = torch.full((1,), 5, dtype=torch.long)
target_lengths = torch.randint(10, 30,(1,), dtype=torch.long)
loss = F.ctc_loss(log_probs, targets, input_lengths, target_lengths)
print(loss)
loss.backward()
print(log_probs.grad)
```

```
tensor(inf, grad_fn=<MeanBackward0>)
tensor([[[nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]],

        [[nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]],

        [[nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]],

        [[nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]],

        [[nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]]])
```
やるやん。



## してはいけない対策

さて、ここまでで皆様はnanを出す方法は大体おわかりいただけたと思います。

しかし、世の中には相反する方もいるもので、学習が進まないとか、デフォルトではnanを検出する機構が搭載されていないからといってnanをなるべく早く検出したいとか、nanを出す・出し続けることをよしとしない方もいらしゃいます。

そこで、ここにそれを書いておきます。

###ここでもう一度タイトルを見なおして、こんな対策はせずに基本に立ち返ってnanを出し続けましょう!!

### torch.isnan()を用いる

PyTorchにはnanを検出するための忌々しい関数があります。

```
import torch                                                                                                                                                 
import numpy as np

x1 = torch.tensor([1])
x2 = torch.tensor([np.nan])
print(x1)
print(x2)
print(torch.isnan(x1))
print(torch.isnan(x2))
```

```
tensor([1])
tensor([nan])
tensor([False])
tensor([True])
```

:cry:

### detect_anomalyを使う

`torch.isnan()`の問題点としてはbackward()時に検出出来ないという点です。

素晴らしい。

しかし、PyTorchも規模の大きい開発チームで開発されています。
これならbackward()時に出ないなら対策できないな!というnan愛好家の甘い考えはあっさり刈り取られます。

[`torch.autograd.detect_anomaly()`](https://pytorch.org/docs/stable/autograd.html#torch.autograd.detect_anomaly)という関数を用意しています。

```
import torch
import torch.nn as nn
from torch.autograd import detect_anomaly

with detect_anomaly():
    x = torch.tensor([1,2,0], dtype=torch.float, requires_grad=True)
    y = torch.sqrt(x)
    loss = torch.nn.functional.mse_loss(x,y)
    loss.backward()
```

```
/opt/conda/conda-bld/pytorch_1565272271120/work/torch/csrc/autograd/python_anomaly_mode.cpp:57: UserWarning: Traceback of forward call that caused the error:
  File "nan_love.py", line 8, in <module>
    y = torch.sqrt(x)

Traceback (most recent call last):
  File "nan_love.py", line 10, in <module>
    loss.backward()
  File "python3.7/site-packages/torch/tensor.py", line 118, in backward
    torch.autograd.backward(self, gradient, retain_graph, create_graph)
  File "python3.7/site-packages/torch/autograd/__init__.py", line 93, in backward
    allow_unreachable=True)  # allow_unreachable flag
RuntimeError: Function 'SqrtBackward' returned nan values in its 0th output.
```

※一部pythonのパス系の表記を変えてますが大体同じ結果が出ます


:skull:

これを見ると「Sqrtのbackward時にnanが出てるで」と言っています。


随時更新予定