PyTorch 2.3の新機能: `torch.nn.utils.stateless.functional_call` でニューラルネットワークを柔軟に評価


従来、ネットワークのパラメータを評価するには、ネットワークのインスタンスを作成し、それぞれのパラメータセットで個別に前向きに伝播させる必要がありました。これは、特に多数のパラメータセットを扱う場合、計算量が多く、時間とコード量の無駄になる可能性がありました。

torch.nn.utils.stateless.functional_call を使用すると、このプロセスを大幅に簡素化できます。この関数は、ネットワークアーキテクチャを単一のインスタンスとして保持し、必要なたびに異なるパラメータセットを渡すことで、さまざまな構成でネットワークを迅速かつ効率的に評価することができます。

機能と利点

  • 推論とデバッグの容易化: ネットワークの動作を異なるパラメータ設定で観察できます。
  • 効率の向上: 複数のパラメータセットを評価する際に、計算コストを削減できます。
  • コードの簡潔化: 冗長なネットワークインスタンスの作成と前向きな伝播処理を排除できます。
  • 柔軟性の向上: さまざまなパラメータセットでネットワークを簡単に評価できます。
import torch

class MyModule(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(10, 1)

# モデルを定義
model = MyModule()

# 異なるパラメータセットでモデルを評価
input = torch.randn(10)

# パラメータセット1
params1 = {'linear.weight': torch.randn(10, 1), 'linear.bias': torch.zeros(1)}
output1 = functional_call(model, input, **params1)
print(output1)

# パラメータセット2
params2 = {'linear.weight': torch.ones(10, 1), 'linear.bias': torch.ones(1)}
output2 = functional_call(model, input, **params2)
print(output2)

この例では、MyModule というシンプルなモデルを定義し、functional_call を使って異なるパラメータセットでモデルを評価しています。

  • この関数は、勾配計算をサポートしていません。勾配計算が必要な場合は、従来の方法でネットワークインスタンスを作成する必要があります。
  • 厳密モード (strict=True) を有効にすると、渡されたパラメータとバッファがモデルのオリジナルと一致していることを確認します。
  • torch.nn.utils.stateless.functional_call は PyTorch 2.3 以降でのみ使用可能です。


畳み込みニューラルネットワークの評価

この例では、 畳み込みニューラルネットワーク (CNN) を定義し、functional_call を使って異なるカーネルとバイアスでネットワークを評価します。

import torch
import torch.nn as nn

class CNN(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)

input = torch.randn(1, in_channels, 28, 28)  # 例として MNIST 画像を使用

# カーネルとバイアスのパラメータセットを定義
params1 = {'conv.weight': torch.randn(out_channels, in_channels, kernel_size, kernel_size),
          'conv.bias': torch.randn(out_channels)}

# パラメータセット1で CNN を評価
output1 = functional_call(CNN(in_channels, out_channels, kernel_size), input, **params1)
print(output1.shape)

# カーネルとバイアスのパラメータセット2を定義
params2 = {'conv.weight': torch.ones(out_channels, in_channels, kernel_size, kernel_size),
          'conv.bias': torch.zeros(out_channels)}

# パラメータセット2で CNN を評価
output2 = functional_call(CNN(in_channels, out_channels, kernel_size), input, **params2)
print(output2.shape)

この例では、 再帰型ニューラルネットワーク (RNN) を定義し、functional_call を使って異なる初期状態と隠れ状態でネットワークからシーケンスを生成します。

import torch
import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_size, hidden_size):
        super().__init__()
        self.cell = nn.GRUCell(input_size, hidden_size)

# シーケンス長と入力サイズを定義
seq_len = 10
input_size = 5

# 初期状態と隠れ状態のパラメータセットを定義
params = {'cell.hidden_state': torch.randn(hidden_size),
         'cell.weight_ih': torch.randn(4 * hidden_size, input_size),
         'cell.weight_hh': torch.randn(4 * hidden_size, hidden_size),
         'cell.bias_ih': torch.randn(4 * hidden_size),
         'cell.bias_hh': torch.randn(4 * hidden_size)}

# 入力シーケンスを定義
inputs = torch.randn(seq_len, input_size)

# 初期状態と隠れ状態のパラメータを使用してシーケンスを生成
outputs = []
for input_t in inputs:
    output_t = functional_call(RNN(input_size, hidden_size), input_t, **params)
    outputs.append(output_t)

# 生成されたシーケンスを連結
outputs = torch.stack(outputs, 0)
print(outputs.shape)

これらの例は、torch.nn.utils.stateless.functional_call の柔軟性を示しています。この関数は、さまざまなニューラルネットワークアーキテクチャとタスクに適用できます。

  • モデルの内部状態を変更せずにネットワークを評価したい場合に、functional_call は強力なツールとなります。
  • functional_call は推論と評価にのみ使用でき、勾配計算には使用できません。
  • 上記の例はあくまで基本的なものであり、実際の使用例ではより複雑なネットワークや操作が含まれる可能性があります。


torch.nn.utils.stateless.functional_call は PyTorch 2.0 で導入されましたが、PyTorch 2.3 で非推奨となり、今後のバージョンで削除される予定です。

代替手段として、以下の方法が推奨されています。

  1. torch.func.functional_call: これは torch.nn.utils.stateless.functional_call の直接的な代替手段であり、機能とインターフェースがほぼ同じです。
import torch
from torch.func import functional_call

# ... (モデルと入力データの定義) ...

output = functional_call(model, input, **params)
  1. 手動パラメータ置換: 以下の手順で、モデルのパラメータとバッファを一時的に置き換えることができます。

  2. モデルのパラメータとバッファを dict に保存します。

  3. 渡されたパラメータでモデルのパラメータとバッファを更新します。

  4. モデルを前向きに伝播させます。

  5. 元のパラメータとバッファを復元します。

import torch

# ... (モデルと入力データの定義) ...

# パラメータを dict に保存
orig_params = dict(model.named_parameters())

# 渡されたパラメータでモデルのパラメータを更新
for name, param in params.items():
    setattr(model, name, param)

# モデルを前向きに伝播
output = model(input)

# 元のパラメータを復元
model.load_state_dict(orig_params)

上記以外にも、状況に応じて以下の選択肢が考えられます。

  • カスタム関数: 独自の関数を作成して、モデルのパラメータとバッファを置換し、前向きに伝播させるロジックをカプセル化することができます。
  • torch.jit.scripttorch.jit.trace: モデルをトレースしてモジュール化することで、推論時に異なるパラメータセットを効率的に評価することができます。
  • モデルの再構築: 異なるパラメータセットごとに個別のモデルインスタンスを作成します。これはメモリ使用量が多くなりますが、コードが最もシンプルでわかりやすい方法です。

最適な代替手段の選択

最適な代替手段は、具体的なユースケースと要件によって異なります。

  • カプセル化: カスタム関数を作成する。
  • パフォーマンス: torch.jit.script または torch.jit.trace を検討する。
  • メモリ効率: モデルの再構築を検討する。
  • 柔軟性と制御: 手動パラメータ置換を使用する。
  • 簡潔性と使いやすさ: torch.func.functional_call を使用する.