Neuro NetworkにおけるパディングとPackedSequence:PyTorchで理解するunsorted_indices


パディングとPackedSequence

ニューラルネットワークで可変長のシーケンスデータを扱う場合、異なる長さのシーケンスを効率的に処理するために、パディングと呼ばれる手法を用います。パディングとは、短いシーケンスを長いシーケンスと同じ長さに揃えるために、末尾に特別な値(通常は0)を挿入することです。

パディングされたシーケンスは、torch.nn.utils.rnn.pack_padded_sequence 関数を使用して PackedSequence 型に変換することができます。PackedSequenceは、パディングされたデータと、各ステップにおけるバッチサイズに関する情報を保持します。

unsorted_indices 属性

PackedSequence オブジェクトには、unsorted_indices 属性と呼ばれる属性があります。この属性は、パディングされたシーケンスデータを元の順序に戻すためのインデックス を保持します。

具体的には、unsorted_indices は、パディング前の各シーケンスの要素が、PackedSequence内のどの位置に対応しているかを表すインデックスのリストです。

unsorted_indices の例

以下は、unsorted_indices 属性の例です。

# パディング前のシーケンス
sequences = [
    [1, 2, 3],
    [4, 5],
    [6]
]

# パディングされたシーケンス
padded_sequences = torch.nn.utils.rnn.pad_sequence(sequences, batch_first=True)

# PackedSequenceに変換
packed_sequence = torch.nn.utils.rnn.pack_padded_sequence(padded_sequences, batch_first=True)

# unsorted_indices 属性を取得
unsorted_indices = packed_sequence.unsorted_indices

print(unsorted_indices)

このコードを実行すると、以下の出力が得られます。

tensor([0, 1, 2, 3, 4, 5])

この出力は、パディング前の各シーケンスの要素が、PackedSequence内のどの位置に対応しているかを表しています。

  • 要素 6 は PackedSequence 内の 5 番目の位置に対応します。
  • ...
  • 要素 2 は PackedSequence 内の 1 番目の位置に対応します。
  • 要素 1 は PackedSequence 内の 0 番目の位置に対応します。

unsorted_indices 属性は、主に以下の用途で使用されます。

  • パディングされたシーケンスデータに対してマスクを適用する
  • パディングされたシーケンスデータを元の順序に戻す

例えば、以下のように unsorted_indices 属性を使用して、パディングされたシーケンスデータを元の順序に戻すことができます。

# パディングされたシーケンス
padded_sequences = torch.nn.utils.rnn.pad_sequence(sequences, batch_first=True)

# PackedSequenceに変換
packed_sequence = torch.nn.utils.rnn.pack_padded_sequence(padded_sequences, batch_first=True)

# unsorted_indices 属性を取得
unsorted_indices = packed_sequence.unsorted_indices

# パディングされたシーケンスデータを元の順序に戻す
unpacked_sequences = torch.nn.utils.rnn.unpack_sequence(packed_sequence, unsorted_indices=unsorted_indices)

print(unpacked_sequences)
[tensor([1, 2, 3]), tensor([4, 5]), tensor([6])]

この出力は、パディング前の元のシーケンスデータが復元されています。



単一のシーケンスに対する例

この例では、単一のシーケンスをパディングし、PackedSequence に変換し、unsorted_indices 属性を使用して元の順序に戻します。

import torch

# シーケンス
sequence = [1, 2, 3, 0, 0]

# パディング長
padding_length = 2

# パディング
padded_sequence = torch.nn.utils.pad_sequence([sequence], batch_first=True, padding_value=0)

# PackedSequenceに変換
packed_sequence = torch.nn.utils.rnn.pack_padded_sequence(padded_sequence, batch_first=True)

# unsorted_indices 属性を取得
unsorted_indices = packed_sequence.unsorted_indices

# パディングされたシーケンスデータを元の順序に戻す
unpacked_sequence = torch.nn.utils.rnn.unpack_sequence(packed_sequence, unsorted_indices=unsorted_indices)

print(unpacked_sequence[0])  # [1, 2, 3]

バッチ内の複数のシーケンスに対する例

import torch

# シーケンス
sequences = [
    [1, 2, 3, 0, 0],
    [4, 5, 0, 0],
    [6, 0, 0]
]

# パディング長
padding_length = 2

# パディング
padded_sequences = torch.nn.utils.pad_sequence(sequences, batch_first=True, padding_value=0)

# PackedSequenceに変換
packed_sequence = torch.nn.utils.rnn.pack_padded_sequence(padded_sequences, batch_first=True)

# unsorted_indices 属性を取得
unsorted_indices = packed_sequence.unsorted_indices

# パディングされたシーケンスデータを元の順序に戻す
unpacked_sequences = torch.nn.utils.rnn.unpack_sequence(packed_sequence, unsorted_indices=unsorted_indices)

# バッチ内の各シーケンスを出力
for sequence in unpacked_sequences:
    print(sequence)
[1, 2, 3]
[4, 5]
[6]

この例では、unsorted_indices 属性を使用して、パディングされたシーケンスデータに対してマスクを適用します。

import torch

# シーケンス
sequence = [1, 2, 3, 0, 0]

# パディング長
padding_length = 2

# パディング
padded_sequence = torch.nn.utils.pad_sequence([sequence], batch_first=True, padding_value=0)

# PackedSequenceに変換
packed_sequence = torch.nn.utils.rnn.pack_padded_sequence(padded_sequence, batch_first=True)

# unsorted_indices 属性を取得
unsorted_indices = packed_sequence.unsorted_indices

# マスクを作成
mask = torch.nn.functional.pad(torch.ones(len(sequence), dtype=torch.bool), (padding_length, 0))

# マスクされたシーケンスデータを取得
masked_sequence = unpacked_sequences[0][mask]

print(masked_sequence)  # [1, 2, 3]

このコードを実行すると、パディングされた部分を除いた元のシーケンスデータが出力されます。



torch.argsort を使用する

torch.argsort 関数は、テンソルの要素を昇順に並べ替えたインデックスを返します。この機能を使用して、パディングされたシーケンスデータを元の順序に戻すことができます。

import torch

# パディングされたシーケンス
padded_sequence = torch.nn.utils.pad_sequence(sequences, batch_first=True)

# バッチサイズを取得
batch_size = padded_sequence.size(0)

# 各バッチのシーケンス長を取得
sequence_lengths = (padded_sequence != 0).sum(dim=1)

# 各バッチ内の要素を昇順に並べ替える
sorted_indices = torch.argsort(padded_sequence, dim=1, stable=True)

# バッチごとに元に戻す
unsorted_indices = []
for i in range(batch_size):
    unsorted_indices.append(sorted_indices[i][sequence_lengths[i] - 1::-1])

unsorted_indices = torch.stack(unsorted_indices, dim=0)

カスタムソート関数を使用する

より柔軟な制御が必要な場合は、カスタムソート関数を使用して unsorted_indices を生成することができます。この関数は、パディングされたシーケンスデータとバッチサイズを引数として受け取り、元の順序を表すインデックスを返す必要があります。

import torch

def custom_sort(padded_sequence, batch_size):
    # ... カスタムソートロジック ...
    unsorted_indices = []
    # ... 各バッチの `unsorted_indices` を計算 ...
    return unsorted_indices

# パディングされたシーケンス
padded_sequence = torch.nn.utils.pad_sequence(sequences, batch_first=True)

# カスタムソート関数を使用して unsorted_indices を生成
unsorted_indices = custom_sort(padded_sequence, batch_size)

torch.nn.utils.rnn.unpack_padded_sequence を使用する

torch.nn.utils.rnn.unpack_padded_sequence 関数は、PackedSequence オブジェクトをアンパックし、パディングのないシーケンスリストを返します。このリストには、元の順序で並べ替えられたシーケンスが含まれています。

import torch

# パディングされたシーケンス
padded_sequence = torch.nn.utils.pad_sequence(sequences, batch_first=True)

# PackedSequence をアンパック
unpacked_sequences, _ = torch.nn.utils.rnn.unpack_padded_sequence(padded_sequence)

注意点

上記で紹介した代替方法は、それぞれ異なる長所と短所を持っています。

  • torch.nn.utils.rnn.unpack_padded_sequence を使用する方法はシンプルでメモリ効率が高いですが、パディングマスクが必要な場合は別途処理する必要があります。
  • カスタムソート関数を使用する方法は柔軟性がありますが、実装が複雑になる可能性があります。
  • torch.argsort を使用する方法はシンプルですが、柔軟性に欠けます。

状況に応じて適切な方法を選択することが重要です。