0.初めに
pytorchのdropout2dはどのように機能しているのでしょうか.
model.eval()とすれば機能せずmodel.train()とすればdropoutしてくれますよね.
使用用途はPyTorchでMNIST by @fukuit などがあります.
ただ,実際どのようにコードが書かれているのか,追えていなかったため
githubを追って確認してみたいと思います.
(結論から言いますが,一部モジュールの中身がよくわからなくなってしまったため,そちらに関しては諦めています.(特にcppとpythonの呼び出しに関して)補足等ありましたら,ご教授いただけると幸いです.)
(この記事は2021年10月7日,8日に作成しております.)
追記 dropout2dとdropoutの違いについて
挙動としては,channelの全てを0にするため,dropoutとは挙動は異なります.
torch.manual_seed(42)
m = nn.Dropout2d(p=0.5)
input = torch.randn(3, 2, 2, 2)
output = m(input)
print(output)
"""
tensor([[[[ 3.8538, 2.9746],
[ 1.8014, -4.2110]],
[[ 1.3568, -2.4691],
[-0.0861, -3.2093]]],
[[[ 0.7117, -1.3732],
[-0.9867, 0.4830]],
[[-0.0000, 0.0000],
[-0.0000, -0.0000]]],
[[[-0.0000, -0.0000],
[ 0.0000, -0.0000]],
[[-0.0000, -0.0000],
[-0.0000, 0.0000]]]])
"""
torch.manual_seed(42)
m = nn.Dropout(p=0.5)
input = torch.randn(3, 2, 2, 2)
output = m(input)
print(output)
"""
tensor([[[[ 3.8538, 2.9746],
[ 1.8014, -0.0000]],
[[ 0.0000, -0.0000],
[-0.0861, -0.0000]]],
[[[ 0.7117, -1.3732],
[-0.0000, 0.0000]],
[[-0.0000, 0.0000],
[-0.0000, -0.0000]]],
[[[-0.0000, -0.0000],
[ 0.0000, -1.2432]],
[[-1.1840, -0.0000],
[-0.0000, 0.6618]]]])
"""
また,仮に画像出ないときのこれらの挙動は一致します.
torch.manual_seed(42)
m = nn.Dropout2d(p=0.5)
input = torch.randn(3, 2)
output = m(input)
print(output)
"""
tensor([[ 0.6734, 0.0000],
[ 0.4689, 0.4607],
[-2.2457, -0.0000]])
"""
torch.manual_seed(42)
m = nn.Dropout(p=0.5)
input = torch.randn(3, 2)
output = m(input)
print(output)
"""
tensor([[ 0.6734, 0.0000],
[ 0.4689, 0.4607],
[-2.2457, -0.0000]])
"""
1. Dropout2d
これはimport torch.nn as nn
でimportすればnn.Dropout2d()
として使用できるのですが,それはtorch.nn.init.pyにてtorch.nn.modules.*
をimportするように設定されていることとtorch.nn.modules.init.pyにdropout.Dropout2d
をimportするように書かれていることにあります.
Dropout2dはtorch.nn.modules.dropout.pyで定義されています.
class Dropout2d(_DropoutNd):
def forward(self, input: Tensor) -> Tensor:
return F.dropout2d(input, self.p, self.training, self.inplace)
とあります.
1.0 _DropoutNd
こちらは,
class _DropoutNd(Module):
__constants__ = ['p', 'inplace']
p: float
inplace: bool
def __init__(self, p: float = 0.5, inplace: bool = False) -> None:
super(_DropoutNd, self).__init__()
if p < 0 or p > 1:
raise ValueError("dropout probability has to be between 0 and 1, "
"but got {}".format(p))
self.p = p
self.inplace = inplace
def extra_repr(self) -> str:
return 'p={}, inplace={}'.format(self.p, self.inplace)
また,Moduleは
from .module import Module
であるため,一つ前のdirにあるtorch.nn.moduels.module.pyにて定義されています.しかし,こちらのクラスはとても長く読むと日が暮れてしまうため
概要を記すと,modelを作成する際に親クラスとして設定するように書かれていました.
そのため,_DropoutNdやDropout2dはモデルの一種として捉えることができます.
1.1 F
Fは
from .. import functional as F
とあるため,二つ前のディレクトリにあるtorch.nn.functional.pyを見てみましょう.
1.1.0 functional.py
ここの1232行目に
def dropout2d(input: Tensor, p: float = 0.5, training: bool = True, inplace: bool = False) -> Tensor:
if has_torch_function_unary(input):
return handle_torch_function(dropout2d, (input,), input, p=p, training=training, inplace=inplace)
if p < 0.0 or p > 1.0:
raise ValueError("dropout probability has to be between 0 and 1, " "but got {}".format(p))
return _VF.feature_dropout_(input, p, training) if inplace else _VF.feature_dropout(input, p, training)
とあります.一つ一つ見てきましょう.
また,最後の行を見るとtrainingのmodeにより,inplaceには依存しないようにも見えます...
また,self.trainingはtorch.nn.modules.module.pyから
eval()やtrain()で変化することがわかります
1.1.0.0 has_torch_function_unary と handle_torch_function
この二つは,
from ..overrides import (
has_torch_function, has_torch_function_unary, has_torch_function_variadic,
handle_torch_function)
とあるため,二つ前のディレクトリにあるtorch.overrides.pyの中を見てみましょう.
1.1.0.0.0 has_torch_function_unary(input)
しかし残念ながら,ここでは
from torch._C import (
_has_torch_function, _has_torch_function_unary,
_has_torch_function_variadic, _add_docstr)
とありここでも定義がされていないようです.
そのため,torch._Cをみてみましょう.
しかし,こちらは一般的なモジュールの書き方がなされていなく,
__init__.pyi.in
の中にある611行目に定義が書いてあるのですが
def _has_torch_function_unary(Any) -> _bool: ... # THPModule_has_torch_function_unary
で中身はよくわかりませんでした(諦).
1.1.0.0.1 handle_torch_function(dropout2d, (input,), input, p=p, training=training, inplace=inplace)
torch.overrides.pyの1308行目から定義が書かれているのですが,こちらはtorchを扱うように定義されていない関数をtorchを扱えるようにする際に使うようです.
つまり,これを噛ませることで,torchで扱える関数となるようです.
ここから逆算し,コードを見るとhas_torch_function_unary(input)
はinputの中身がtorchかどうかをみているように思えます.
1.1.0.1 _VF
これは
from torch import _VF
でimportされているためtorch._VF.pyの中身を見てみることにします.しかし,feature_dropout_()
に関する定義は見当たりません.
そこでimportされているものを確認してみることにします.
import types
from typing import Any, List, Sequence, Tuple, Union
import builtins
とあるため,torch.types.pyを確認してみます.
しかし,types.ModuleTypeに該当するものがありません...(?)
ただ,これは勘違いで,実はこれに関してはPythonのdefaultのtypesをimportしていました.
実際にtorch.types.pyをimportする場合は.types
としなければなリませんでしたね.
このように,このファイルには公式ドキュメントに用意されているものしかimportしていないことがわかりました.
では,どこでfeature_dropout_()
は定義されているのでしょうか.
現状の主にわかっていない点は以下の二つです.
- A : どこて
feature_dropoout_
が定義されているか - B : どのようにして
_VF.feature_dropout_
の形で呼び出しているのか
1.1.0.1.A feature_dropout_ を探す旅
候補としてpytorch/aten/src/ATen/native/Dropout.cppの105行目に定義され
c++のコードをpythonにimportしてきているようです.
Tensor& feature_dropout_(Tensor& input, double p, bool train) {
return _feature_dropout<true>(input, p, train);
}
_feature_dropoutは79行目に
ALIAS_SPECIALIZATION(_feature_dropout, true, false)
とありました.
ALIAS_SPECIALIZATIONは72行目に
#define ALIAS_SPECIALIZATION(ALIAS_NAME, IS_FEATURE, IS_ALPHA) \
template <bool inplace, typename... Args> \
Ctype<inplace> ALIAS_NAME(Args&&... args) { \
return _dropout_impl<IS_FEATURE, IS_ALPHA, inplace>(std::forward<Args>(args)...); \
}
とあります.
そして,_dropout_implは
template<bool feature_dropout, bool alpha_dropout, bool inplace, typename T>
Ctype<inplace> _dropout_impl(T& input, double p, bool train) {
TORCH_CHECK(p >= 0 && p <= 1, "dropout probability has to be between 0 and 1, but got ", p);
if (p == 0 || !train || input.numel() == 0) {
return input;
}
if (p == 1) {
return multiply<inplace>(input, at::zeros({}, input.options()));
}
at::Tensor b; // used for alpha_dropout only
auto noise = feature_dropout ? make_feature_noise(input) : at::empty_like(input, LEGACY_CONTIGUOUS_MEMORY_FORMAT);
noise.bernoulli_(1 - p);
if (alpha_dropout) {
constexpr double alpha = 1.7580993408473766;
double a = 1. / std::sqrt((alpha * alpha * p + 1) * (1 - p));
b = noise.add(-1).mul_(alpha * a).add_(alpha * a * p);
noise.mul_(a);
} else {
noise.div_(1 - p);
}
if (!alpha_dropout) {
return multiply<inplace>(input, noise);
} else {
return multiply<inplace>(input, noise).add_(b);
}
}
とありました.
しかし,c++とpythonの呼び出しに関して,勉強不足の面があるため
こちらは別の機会で追えたらと思います.
とにかく,開発にあたりc++の勉強周りもする必要があると感じた次第です.
Cで作った関数をpythonで呼ぶ by @nabionや
組み込みエンジニアの戸惑い PythonからC言語を呼び出してみる①(Python/C APIを使った場合)が参考になるかもしれません.
1.1.0.1.B どのようにして呼び出しているか
こちらに関しては,現状わからなかったため今回は諦めて,次の機会に追っていきたいと思います.
2. 後書き
だいぶ投げやりな記事になってしまいましたが,いかがでしたでしょうか.私としては,pytorchのmoduleを追う際には少なくともc++の知識も必要になってくることを実感できてよかったかと思います.
ただ,Dropout.cppの中身を読むとにtrainがfalse(0)であれば,そのままinputを返すように伺えたため,実際に作成したモデルがtrainモードにdropoutが機能し,evalモードではdropoutが起動しないことは確認できたため,当初の目的である『どのように機能しているか』はある程度理解できた気がします.
次の機会では,c++とpythonの呼び出し関係について勉強し,こちらの記事の補足ができたらなと思います.
補足等ありましたら何卒よろしくお願い致します.