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
を使用する方法はシンプルですが、柔軟性に欠けます。
状況に応じて適切な方法を選択することが重要です。