Hessian行列やヤコビアンベクトルも計算可能!PyTorchのtorch.func.grad_and_value()の応用例


PyTorch 1.10 には、JAX ライクな関数変換と呼ばれる新機能が導入されました。これは、関数の自動微分 (AD) と制御フローをより柔軟かつ強力に制御するためのツールセットです。この機能の一部として、torch.func.grad_and_value() 関数も導入されました。

本記事では、JAXライクな関数変換torch.func.grad_and_value() 関数について、分かりやすく解説します。

JAXライクな関数変換とは?

JAXライクな関数変換は、関数の自動微分 (AD) と制御フローをより柔軟かつ強力に制御するためのツールセットです。従来の PyTorch の AD とは異なり、以下の点が特徴です。

  • より強力な機能: hessianjvp などの新しい関数を使用して、Hessian 行列やヤコビアンベクトルなどの高階導数情報を計算できます。
  • 制御フローをより柔軟に制御できる: vmapscan などの新しい関数を使用して、ループや条件分岐などの複雑な制御フローを自動微分できます。
  • 関数に直接注釈を付けられる: 関数内部のコードを変更することなく、自動微分 (AD) の設定を指定できます。

torch.func.grad_and_value() 関数

torch.func.grad_and_value() 関数は、JAXライクな関数変換の一部として導入された関数です。この関数は、以下の機能を提供します。

  • 補助オブジェクトのサポート: has_aux オプションを True に設定すると、関数の補助オブジェクトも返します。これは、モデルの出力だけでなく、中間的な計算結果なども取得したい場合に役立ちます。
  • 関数の勾配と値を同時に計算: 関数を入力と出力の両方に対して計算し、勾配と値をタプルとして返します。

以下の例は、torch.func.grad_and_value() 関数を使用して、簡単な関数の勾配と値を計算する方法を示しています。

import torch
import torch.func

def square(x):
  return x * x

x = torch.tensor(2.0)
grad_and_value, aux = torch.func.grad_and_value(square, has_aux=True)
grad = grad_and_value[0]
value = grad_and_value[1]

print(grad)  # 4.0
print(value)  # 4.0
print(aux)  # None

この例では、square 関数の勾配と値を計算し、それぞれ gradvalue 変数に格納しています。また、has_aux オプションを True に設定しているので、aux 変数には補助オブジェクトが格納されますが、この例では None になっています。



import torch
import torch.func

def square(x):
  return x * x

x = torch.linspace(0, 10, 100)
y = torch.func.vmap(square)(x)
grad = torch.autograd.grad(y, x)

print(grad)

この例では、square 関数を vmap 関数を使用してベクトル化し、x テンサーの各要素に対して適用します。その後、torch.autograd.grad 関数を使用して、y テンサーに対する x テンサーの勾配を計算します。

例 2: 条件分岐内の自動微分

この例では、scan 関数を使用して、条件分岐内の自動微分を行います。

import torch
import torch.func

def square_if_even(x):
  if x % 2 == 0:
    return x * x
  else:
    return x

x = torch.tensor([1, 2, 3, 4])
y, aux = torch.func.scan(square_if_even, x, has_aux=True)
grad = torch.autograd.grad(y, x)

print(grad)
print(aux)

この例では、square_if_even 関数を scan 関数を使用してベクトル化し、x テンサーの各要素に対して適用します。square_if_even 関数は、x が偶数の場合のみ平方を計算します。その後、torch.autograd.grad 関数を使用して、y テンサーに対する x テンサーの勾配を計算します。また、has_aux オプションを True に設定しているので、aux 変数には補助オブジェクトが格納されます。この補助オブジェクトには、各要素が偶数か奇数かの情報が含まれています。

例 3: Hessian 行列の計算

この例では、hessian 関数を使用して、関数の Hessian 行列を計算します。

import torch
import torch.func

def square(x):
  return x * x

x = torch.tensor(2.0)
hessian = torch.func.hessian(square)(x)

print(hessian)

この例では、square 関数の Hessian 行列を計算し、hessian 変数に格納します。Hessian 行列は、関数の二階導関数行列を表します。

例 4: ヤコビアンベクトルの計算

この例では、jvp 関数を使用して、関数のヤコビアンベクトルを計算します。

import torch
import torch.func

def square(x):
  return x * x

x = torch.tensor(2.0)
v = torch.tensor(1.0)
jvp = torch.func.jvp(square, x, v)

print(jvp)

この例では、square 関数のヤコビアンベクトルを計算し、jvp 変数に格納します。ヤコビアンベクトルは、関数の入力ベクトルに対する出力ベクトルの勾配ベクトルを表します。



代替方法の検討

torch.func.grad_and_value() の代替方法を検討する際には、以下の点を考慮する必要があります。

  • パフォーマンス: 計算速度はどの程度重要なのか?
  • 関数の複雑性: 関数は単純なのか、複雑な制御フローを含むのか?
  • 計算の目的: 勾配のみが必要なのか、値も必要なのか?

代替方法の例

以下に、torch.func.grad_and_value() の代替方法の例をいくつか示します。

  • 手動微分: 関数が単純な場合、手動で微分を計算することもできます。これは、特に教育目的の場合に役立ちます。
  • torch.autograd: 関数の勾配のみが必要であれば、torch.autograd モジュールを使用するのが最も一般的です。torch.autograd は、シンプルな関数に対して効率的に動作します。

例:シンプルな関数の勾配を計算する場合

以下の例は、torch.autograd を使用して、シンプルな関数の勾配を計算する方法を示しています。

import torch

def square(x):
  return x * x

x = torch.tensor(2.0)
y = square(x)
grad = torch.autograd.grad(y, x)

print(grad)  # 4.0

この例では、square 関数の勾配を計算し、grad 変数に格納しています。

例:複雑な制御フローを含む関数の勾配を計算する場合

以下の例は、torch.func.vmap 関数を使用して、複雑な制御フローを含む関数の勾配を計算する方法を示しています。

import torch
import torch.func

def square_if_even(x):
  if x % 2 == 0:
    return x * x
  else:
    return x

x = torch.tensor([1, 2, 3, 4])
y = torch.func.vmap(square_if_even)(x)
grad = torch.autograd.grad(y, x)

print(grad)

この例では、square_if_even 関数を torch.func.vmap 関数を使用してベクトル化し、x テンサーの各要素に対して適用します。その後、torch.autograd.grad 関数を使用して、y テンサーに対する x テンサーの勾配を計算します。

例:手動で微分の計算を行う場合

以下の例は、手動で微分の計算を行う方法を示しています。

def square(x):
  return x * x

x = 2.0
y = square(x)
dy_dx = 2 * x

print(dy_dx)  # 4.0

この例では、square 関数の微分を dy_dx 変数に格納しています。

以下の例は、JAX を使用して関数の勾配を計算する方法を示しています。

import jax.numpy as jnp
import jax

def square(x):
  return x * x

x = jnp.array(2.0)
y = square(x)
grad = jax.grad(square)(x)

print(grad)  # 4.0

この例では、JAX を使用して square 関数の勾配を計算し、grad 変数に格納しています。