LoginSignup
3
1

More than 1 year has passed since last update.

pytorch dropout2dについて

Last updated at Posted at 2021-10-08

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.pydropout.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の呼び出し関係について勉強し,こちらの記事の補足ができたらなと思います.

補足等ありましたら何卒よろしくお願い致します.

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1