PyTorch自動微分:`torch.autograd.function.FunctionCtx.mark_dirty()` を使ってカスタム関数を作成する方法


torch.autograd.function.FunctionCtx.mark_dirty() は、PyTorch の自動微分機能において、インプレイス操作で変更されたテンソルを検出するために使用される関数です。この関数は、Function サブクラスの forward() メソッド内で呼び出され、計算グラフ内でのテンソルの更新を追跡するために重要です。

mark_dirty() 関数の役割

自動微分では、計算グラフと呼ばれるグラフ構造を用いて、各操作間の依存関係を表現します。このグラフは、出力テンソルから入力テンソルへの勾配を計算するために使用されます。

インプレイス操作は、テンソルを直接変更する操作であり、計算グラフの構造を変更する可能性があります。mark_dirty() 関数は、このような変更を検出し、計算グラフを更新するために使用されます。

具体的には、mark_dirty() 関数は、以下の役割を果たします。

  • 勾配計算に必要な情報を保持します。
  • 計算グラフ内の対応するノードを更新します。
  • インプレイス操作によって変更されたテンソルを特定します。

mark_dirty() 関数の使用方法

mark_dirty() 関数は、Function サブクラスの forward() メソッド内で呼び出されます。このメソッドは、計算グラフ内の各ノードに対応する操作を実行します。

インプレイス操作を実行する場合は、mark_dirty() 関数を使用して、変更されたテンソルを明示的に指定する必要があります。これにより、自動微分機能が正しく動作し、出力テンソルに対する勾配を計算できるようになります。

以下の例は、mark_dirty() 関数の使用方法を示しています。

import torch

class MyFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, input):
        output = input.add_(1)
        ctx.mark_dirty(input)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        return grad_output

input = torch.tensor(10)
output = MyFunction.apply(input)
output.backward()
print(input.grad)  # 1.0

この例では、MyFunction クラスは、入力テンソルに 1 を加算するインプレイス操作を実行します。forward() メソッド内で、mark_dirty() 関数を使用して、input テンソルが変更されたことを明示的に指定しています。

torch.autograd.function.FunctionCtx.mark_dirty() 関数は、PyTorch の自動微分機能において、インプレイス操作で変更されたテンソルを検出するために使用されます。この関数は、計算グラフの更新と勾配計算の精度を維持するために重要です。

  • mark_dirty() 関数は、PyTorch 1.6 以降で使用できます。
  • インプレイス操作を実行しない場合は、mark_dirty() 関数を呼び出す必要はありません。
  • mark_dirty() 関数は、Function サブクラスの forward() メソッド内で のみ 呼び出すことができます。


例 1: 単純なインプレイス操作

この例は、input テンソルに 1 を加算するインプレイス操作を実行するカスタム関数 MyFunction を定義します。

import torch

class MyFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, input):
        output = input.add_(1)
        ctx.mark_dirty(input)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        return grad_output

input = torch.tensor(10)
output = MyFunction.apply(input)
output.backward()
print(input.grad)  # 1.0

例 2: 複数のインプレイス操作

この例は、input1 テンソルを input2 テンソルで乗算し、その結果に 1 を加算するインプレイス操作を実行するカスタム関数 MyFunction を定義します。

import torch

class MyFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, input1, input2):
        output = input1.mul_(input2).add_(1)
        ctx.mark_dirty(input1, input2)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        input1, input2 = ctx.saved_tensors
        grad_input1 = grad_output.mul_(input2)
        grad_input2 = grad_output.mul_(input1)
        return grad_input1, grad_input2

input1 = torch.tensor(2)
input2 = torch.tensor(3)
output = MyFunction.apply(input1, input2)
output.backward()
print(input1.grad)  # 3.0
print(input2.grad)  # 2.0

例 3: 条件付きインプレイス操作

この例は、input テンソルの値が 0 より大きい場合にのみインプレイス操作を実行するカスタム関数 MyFunction を定義します。

import torch

class MyFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, input):
        if input > 0:
            output = input.add_(1)
            ctx.mark_dirty(input)
        else:
            output = input
        return output

    @staticmethod
    def backward(ctx, grad_output):
        if ctx.saved_tensor is not None:
            return grad_output
        else:
            return torch.zeros_like(input)

input = torch.tensor(-1)
output = MyFunction.apply(input)
output.backward()
print(input.grad)  # 0.0

input = torch.tensor(5)
output = MyFunction.apply(input)
output.backward()
print(input.grad)  # 1.0

これらの例は、torch.autograd.function.FunctionCtx.mark_dirty() 関数の使用方法を理解するための出発点として役立ちます。具体的な状況に応じて、この関数をさまざまな方法で使用することができます。

  • mark_dirty() 関数は、インプレイス操作によって変更されたテンソルを明示的に指定するために使用されます。
  • backward() メソッドは、出力テンソルに対する勾配を計算します。
  • forward() メソッドは、計算グラフ内の各ノードに対応する操作を実行します。
  • 上記の例では、カスタム関数 MyFunction を定義しています。これは、torch.autograd.Function サブクラスを継承したクラスです。


代替方法

torch.autograd.function.FunctionCtx.mark_dirty() の代替方法として、以下の 2 つの方法が用意されています。

as_tensor() を使用する

as_tensor() 関数は、テンソルを明示的に作成し、計算グラフに追加します。インプレイス操作でテンソルを変更する場合は、as_tensor() 関数を使用して変更後のテンソルを作成することで、自動微分機能が正しく動作するようにすることができます。

import torch

class MyFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, input):
        output = input.add_(1)
        output = output.as_tensor()  # `as_tensor()` を使用する
        ctx.mark_dirty(output)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        return grad_output

input = torch.tensor(10)
output = MyFunction.apply(input)
output.backward()
print(input.grad)  # 1.0

with torch.no_grad(): ブロックを使用する

with torch.no_grad(): ブロックを使用すると、そのブロック内の操作は計算グラフに追加されません。したがって、インプレイス操作でテンソルを変更しても、自動微分機能の影響を受けません。

import torch

class MyFunction(torch.autograd.Function):

    @staticmethod
    def forward(ctx, input):
        with torch.no_grad():
            output = input.add_(1)
        ctx.mark_dirty(output)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        return grad_output

input = torch.tensor(10)
output = MyFunction.apply(input)
output.backward()
print(input.grad)  # 1.0

どちらの方法を選択するべきか

どちらの方法を選択するかは、状況によって異なります。

  • with torch.no_grad(): ブロックを使用する 方法は、インプレイス操作でテンソルを変更する必要がなく、計算グラフに追加したくない場合に適しています。この方法は、計算グラフを更新しないため、より高速に実行できます。
  • as_tensor() を使用する 方法は、インプレイス操作でテンソルを変更する必要がある場合に適しています。この方法は、計算グラフを明示的に更新するため、より正確な結果が得られます。

torch.autograd.function.FunctionCtx.mark_dirty() は非推奨となったため、将来的には使用しなくなる必要があります。上記の代替方法を使用して、インプレイス操作で変更されたテンソルを検出することができます。

  • mark_dirty() 関数は、インプレイス操作によって変更されたテンソルを明示的に指定するために使用されます。
  • backward() メソッドは、出力テンソルに対する勾配を計算します。
  • forward() メソッドは、計算グラフ内の各ノードに対応する操作を実行します。
  • 上記の例では、カスタム関数 MyFunction を定義しています。これは、torch.autograd.Function サブクラスを継承したクラスです。