Các câu trả lời trên đã giải quyết rất tốt câu hỏi tại sao . Tôi chỉ muốn thêm một ví dụ để hiểu rõ hơn về việc sử dụng pack_padded_sequence
.
Hãy lấy một ví dụ
Lưu ý: pack_padded_sequence
yêu cầu các chuỗi được sắp xếp trong lô (theo thứ tự giảm dần độ dài chuỗi). Trong ví dụ dưới đây, lô trình tự đã được sắp xếp để bớt lộn xộn hơn. Truy cập liên kết ý chính này để triển khai đầy đủ.
Đầu tiên, chúng ta tạo một lô gồm 2 chuỗi có độ dài trình tự khác nhau như bên dưới. Chúng tôi có 7 yếu tố trong lô hoàn toàn.
- Mỗi chuỗi có kích thước nhúng là 2.
- Dãy thứ nhất có độ dài: 5
- Dãy thứ hai có độ dài: 2
import torch
seq_batch = [torch.tensor([[1, 1],
[2, 2],
[3, 3],
[4, 4],
[5, 5]]),
torch.tensor([[10, 10],
[20, 20]])]
seq_lens = [5, 2]
Chúng tôi pad seq_batch
để nhận được lô các chuỗi có độ dài bằng 5 (Độ dài tối đa trong lô). Bây giờ, lô mới có 10 phần tử hoàn toàn.
padded_seq_batch = torch.nn.utils.rnn.pad_sequence(seq_batch, batch_first=True)
"""
>>>padded_seq_batch
tensor([[[ 1, 1],
[ 2, 2],
[ 3, 3],
[ 4, 4],
[ 5, 5]],
[[10, 10],
[20, 20],
[ 0, 0],
[ 0, 0],
[ 0, 0]]])
"""
Sau đó, chúng tôi đóng gói padded_seq_batch
. Nó trả về một bộ hai tenxơ:
- Đầu tiên là dữ liệu bao gồm tất cả các phần tử trong lô trình tự.
- Thứ hai là
batch_sizes
sẽ cho biết các yếu tố liên quan với nhau như thế nào theo các bước.
packed_seq_batch = torch.nn.utils.rnn.pack_padded_sequence(padded_seq_batch, lengths=seq_lens, batch_first=True)
"""
>>> packed_seq_batch
PackedSequence(
data=tensor([[ 1, 1],
[10, 10],
[ 2, 2],
[20, 20],
[ 3, 3],
[ 4, 4],
[ 5, 5]]),
batch_sizes=tensor([2, 2, 1, 1, 1]))
"""
Bây giờ, chúng tôi chuyển bộ tuple packed_seq_batch
đến các mô-đun lặp lại trong Pytorch, chẳng hạn như RNN, LSTM. Điều này chỉ yêu cầu 5 + 2=7
tính toán trong mô-đun định kỳ.
lstm = nn.LSTM(input_size=2, hidden_size=3, batch_first=True)
output, (hn, cn) = lstm(packed_seq_batch.float())
"""
>>> output # PackedSequence
PackedSequence(data=tensor(
[[-3.6256e-02, 1.5403e-01, 1.6556e-02],
[-6.3486e-05, 4.0227e-03, 1.2513e-01],
[-5.3134e-02, 1.6058e-01, 2.0192e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01],
[-5.9372e-02, 1.0934e-01, 4.1991e-01],
[-6.0768e-02, 7.0689e-02, 5.9374e-01],
[-6.0125e-02, 4.6476e-02, 7.1243e-01]], grad_fn=<CatBackward>), batch_sizes=tensor([2, 2, 1, 1, 1]))
>>>hn
tensor([[[-6.0125e-02, 4.6476e-02, 7.1243e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01]]], grad_fn=<StackBackward>),
>>>cn
tensor([[[-1.8826e-01, 5.8109e-02, 1.2209e+00],
[-2.2475e-04, 2.3041e-05, 1.4254e-01]]], grad_fn=<StackBackward>)))
"""
Chúng tôi cần chuyển đổi output
trở lại lô đầu ra được đệm:
padded_output, output_lens = torch.nn.utils.rnn.pad_packed_sequence(output, batch_first=True, total_length=5)
"""
>>> padded_output
tensor([[[-3.6256e-02, 1.5403e-01, 1.6556e-02],
[-5.3134e-02, 1.6058e-01, 2.0192e-01],
[-5.9372e-02, 1.0934e-01, 4.1991e-01],
[-6.0768e-02, 7.0689e-02, 5.9374e-01],
[-6.0125e-02, 4.6476e-02, 7.1243e-01]],
[[-6.3486e-05, 4.0227e-03, 1.2513e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]]],
grad_fn=<TransposeBackward0>)
>>> output_lens
tensor([5, 2])
"""
So sánh nỗ lực này với cách tiêu chuẩn
Theo cách tiêu chuẩn, chúng ta chỉ cần chuyển padded_seq_batch
đến lstm
mô-đun. Tuy nhiên, nó yêu cầu 10 lần tính toán. Nó liên quan đến một số tính toán nhiều hơn trên các phần tử đệm sẽ không hiệu quả về mặt tính toán .
Lưu ý rằng nó không dẫn đến các biểu diễn không chính xác , nhưng cần nhiều logic hơn để trích xuất các biểu diễn chính xác.
- Đối với LSTM (hoặc bất kỳ mô-đun lặp lại nào) chỉ có hướng về phía trước, nếu chúng ta muốn trích xuất vectơ ẩn của bước cuối cùng làm đại diện cho một chuỗi, chúng ta sẽ phải lấy vectơ ẩn từ bước T (thứ), trong đó T là độ dài của đầu vào. Chọn đại diện cuối cùng sẽ không chính xác. Lưu ý rằng T sẽ khác nhau đối với các đầu vào khác nhau trong lô.
- Đối với LSTM hai hướng (hoặc bất kỳ mô-đun lặp lại nào), nó thậm chí còn cồng kềnh hơn, vì người ta sẽ phải duy trì hai mô-đun RNN, một mô-đun hoạt động với đệm ở đầu đầu vào và một có đệm ở cuối đầu vào, và cuối cùng là giải nén và nối các vectơ ẩn như đã giải thích ở trên.
Hãy xem sự khác biệt:
output, (hn, cn) = lstm(padded_seq_batch.float())
"""
>>> output
tensor([[[-3.6256e-02, 1.5403e-01, 1.6556e-02],
[-5.3134e-02, 1.6058e-01, 2.0192e-01],
[-5.9372e-02, 1.0934e-01, 4.1991e-01],
[-6.0768e-02, 7.0689e-02, 5.9374e-01],
[-6.0125e-02, 4.6476e-02, 7.1243e-01]],
[[-6.3486e-05, 4.0227e-03, 1.2513e-01],
[-4.3123e-05, 2.3017e-05, 1.4112e-01],
[-4.1217e-02, 1.0726e-01, -1.2697e-01],
[-7.7770e-02, 1.5477e-01, -2.2911e-01],
[-9.9957e-02, 1.7440e-01, -2.7972e-01]]],
grad_fn= < TransposeBackward0 >)
>>> hn
tensor([[[-0.0601, 0.0465, 0.7124],
[-0.1000, 0.1744, -0.2797]]], grad_fn= < StackBackward >),
>>> cn
tensor([[[-0.1883, 0.0581, 1.2209],
[-0.2531, 0.3600, -0.4141]]], grad_fn= < StackBackward >))
"""
Kết quả trên cho thấy rằng hn
, cn
khác nhau theo hai cách trong khi output
từ hai cách dẫn đến các giá trị khác nhau cho các phần tử đệm.