क्यों हम pytorch में दृश्यों "पैक" करते हैं?


99

मैं दोहराने की कोशिश कर रहा था कि rnn के लिए चर-लंबाई अनुक्रम इनपुट के लिए पैकिंग का उपयोग कैसे करें लेकिन मुझे लगता है कि मुझे यह समझने की आवश्यकता है कि हमें अनुक्रम को "पैक" करने की आवश्यकता क्यों है।

मैं समझता हूं कि हमें उन्हें "पैड" करने की आवश्यकता क्यों है लेकिन "पैकिंग" (थ्रू pack_padded_sequence) क्यों आवश्यक है?

किसी भी उच्च स्तरीय स्पष्टीकरण की सराहना की जाएगी!


: pytorch में पैकिंग के बारे में सभी सवालों के discuss.pytorch.org/t/...
चार्ली पार्कर

जवाबों:


92

मैंने इस समस्या पर भी ठोकर खाई है और नीचे जो मैंने सोचा है।

जब RNN (LSTM या GRU या वेनिला-RNN) को प्रशिक्षित करते हैं, तो चर लंबाई अनुक्रमों को बैचना मुश्किल होता है। उदाहरण के लिए: यदि 8 बैच के आकार में अनुक्रमों की लंबाई [4,6,8,5,4,3,7,7,8] है, तो आप सभी अनुक्रमों को पैड कर देंगे और इसके परिणामस्वरूप लंबाई के 8 अनुक्रम होंगे। आप 64 संगणनाओं (8x8) को समाप्त करना होगा, लेकिन आपको केवल 45 संगणनाएँ करने की आवश्यकता थी। इसके अलावा, यदि आप एक द्विदिश-आरएनएन का उपयोग करके कुछ फैंसी करना चाहते हैं, तो बैच की गणना केवल पेडिंग द्वारा करना कठिन होगा और आप आवश्यकता से अधिक कम्प्यूटिंग कर सकते हैं।

इसके बजाय, PyTorch हमें अनुक्रम पैक करने की अनुमति देता है, आंतरिक रूप से पैक अनुक्रम दो सूचियों का एक समूह है। एक में दृश्यों के तत्व होते हैं। तत्वों को समय के चरणों के अनुसार वर्गीकृत किया जाता है (नीचे उदाहरण देखें) और अन्य में प्रत्येक चरण में बैच अनुक्रम का प्रत्येक आकार होता है । यह वास्तविक अनुक्रमों को पुनर्प्राप्त करने के साथ-साथ आरएनएन को यह बताने में सहायक है कि प्रत्येक समय कदम पर बैच का आकार क्या है। यह @Aerin द्वारा इंगित किया गया है। यह आरएनएन को पारित किया जा सकता है और यह आंतरिक रूप से गणनाओं का अनुकूलन करेगा।

मैं कुछ बिंदुओं पर स्पष्ट नहीं हो पाया हूं, इसलिए मुझे बताएं और मैं और स्पष्टीकरण जोड़ सकता हूं।

यहाँ एक कोड उदाहरण है:

 a = [torch.tensor([1,2,3]), torch.tensor([3,4])]
 b = torch.nn.utils.rnn.pad_sequence(a, batch_first=True)
 >>>>
 tensor([[ 1,  2,  3],
    [ 3,  4,  0]])
 torch.nn.utils.rnn.pack_padded_sequence(b, batch_first=True, lengths=[3,2])
 >>>>PackedSequence(data=tensor([ 1,  3,  2,  4,  3]), batch_sizes=tensor([ 2,  2,  1]))

4
क्या आप बता सकते हैं कि दिए गए उदाहरण का आउटपुट PackedSequence (डेटा = टेंसर ([1, 3, 2, 4, 3]), बैच_साइज़ = टेनसर ([2, 2, 1])) क्यों है?
तपस्वी 652

3
डेटा का हिस्सा समय अक्ष के साथ समाप्‍त होता है। बैच_साइज वास्तव में प्रत्येक समय कदम पर बैच आकार की सरणी है।
उमंग गुप्ता

3
बैच_साइज = [2, 2, 1] क्रमशः समूहन [1, 3] [2, 4] और [3] का प्रतिनिधित्व करता है।
चैतन्य शिवडे

@ChaitanyaShivade बैच का आकार [2,2,1] क्यों है? क्या यह [1,2,2] नहीं हो सकता है? इसके पीछे क्या तर्क है?
अनाम प्रोग्रामर

1
क्योंकि चरण t पर, आप केवल चरण t पर वैक्टर की प्रक्रिया कर सकते हैं, यदि आप वैक्टर्स को [1,2,2] के रूप में ऑर्डर करते हैं, तो आप संभवतः प्रत्येक इनपुट को एक बैच के रूप में डाल रहे हैं, लेकिन इसे समानांतर नहीं किया जा सकता है और इसलिए बल्लेबाजी करने योग्य नहीं है
उमंग गुप्ता

60

यहां कुछ दृश्य स्पष्टीकरण 1 हैं जो कार्यक्षमता की कार्यक्षमता के लिए बेहतर अंतर्ज्ञान विकसित करने में मदद कर सकते हैंpack_padded_sequence()

मान लेते हैं कि हमारे पास 6कुल (अनुक्रमिक लंबाई के) अनुक्रम हैं। आप इस संख्या 6को batch_sizeहाइपरपरमीटर के रूप में भी मान सकते हैं । ( batch_sizeअनुक्रम की लंबाई के आधार पर भिन्न होगा (cf. नीचे Fig.2))

अब, हम इन अनुक्रमों को कुछ आवर्तक तंत्रिका नेटवर्क वास्तुकला (ओं) को पारित करना चाहते हैं। ऐसा करने के लिए, हमें 0अपने बैच (आमतौर पर s) के सभी को हमारे बैच में अधिकतम अनुक्रम लंबाई ( max(sequence_lengths)) के साथ पैड करना होगा , जो कि नीचे की आकृति में है 9

गद्देदार-seqs

तो, डेटा तैयार करने का काम अब तक पूरा होना चाहिए, है ना? वास्तव में नहीं .. क्योंकि वास्तव में आवश्यक गणना की तुलना में हमें अभी भी एक गणना समस्या है, मुख्य रूप से हमें कितनी गणना करनी है।

समझ के लिए, आइए हम यह भी मान लें कि हम padded_batch_of_sequencesआकार (6, 9)के वजन मैट्रिक्स Wके साथ आकार के ऊपर गुणा करेंगे (9, 3)

इस प्रकार, हमें 6x9 = 54गुणा और 6x8 = 48जोड़                     ( nrows x (n-1)_cols) संचालन करना होगा, केवल गणना किए गए अधिकांश परिणामों को फेंकना होगा क्योंकि वे 0एस (जहां हमारे पास पैड हैं)। इस मामले में वास्तविक आवश्यक गणना इस प्रकार है:

 9-mult  8-add 
 8-mult  7-add 
 6-mult  5-add 
 4-mult  3-add 
 3-mult  2-add 
 2-mult  1-add
---------------
32-mult  26-add
   
------------------------------  
#savings: 22-mult & 22-add ops  
          (32-54)  (26-48) 

यह बहुत ही सरल ( खिलौना ) उदाहरण के लिए बहुत अधिक बचत है । अब आप कल्पना कर सकते हैं कि कितनी गणना (अंततः: लागत, ऊर्जा, समय, कार्बन उत्सर्जन आदि) pack_padded_sequence()लाखों प्रविष्टियों के साथ बड़े टेंसरों का उपयोग करके बचाया जा सकता है , और दुनिया भर में ऐसा करने वाले मिलियन + सिस्टम, बार-बार।

pack_padded_sequence()उपयोग किए गए रंग-कोडिंग की मदद से नीचे की आकृति से कार्यक्षमता को समझा जा सकता है:

पैक-गद्देदार-seqs

उपयोग करने के परिणामस्वरूप pack_padded_sequence(), हमें उपरोक्त उदाहरण के लिए sequences, (ii) समतल (अक्ष -1 के साथ, समतल) युक्त दसियों का टूपल मिलेगा , (ii) tensor([6,6,5,4,3,3,2,2,1])उपरोक्त उदाहरण के लिए संबंधित बैच आकार ।

डेटा टेंसर (यानी चपटा अनुक्रम) तो नुकसान गणना के लिए CrossEntropy जैसे उद्देश्य कार्यों के लिए पारित किया जा सकता है।


1 छवि क्रेडिट @sgrvinod को


2
बहुत बढ़िया चित्र!
डेविड वाटरवर्थ

1
संपादित करें: मुझे लगता है कि stackoverflow.com/a/55805785/6167850 (नीचे) मेरे सवाल का जवाब देता है, जिसे मैं वैसे भी यहां छोड़ दूंगा: ~ क्या इसका मतलब यह है कि ग्रेडिएंट्स गद्देदार इनपुट के लिए प्रचारित नहीं हैं? क्या होगा यदि मेरा नुकसान फ़ंक्शन केवल आरएनएन के अंतिम छिपे हुए राज्य / आउटपुट पर गणना की जाती है? क्या दक्षता लाभ को फेंक दिया जाना चाहिए? या नुकसान कदम से पहले गणना की जाएगी जहां पैडिंग शुरू होती है, जो इस उदाहरण में प्रत्येक बैच तत्व के लिए अलग है? ~
nlml

28

उपर्युक्त उत्तरों ने इस प्रश्न को संबोधित किया कि क्यों बहुत अच्छी तरह से। मैं केवल बेहतर उपयोग को समझने के लिए एक उदाहरण जोड़ना चाहता हूं pack_padded_sequence

एक उदाहरण लेते हैं

नोट: pack_padded_sequenceबैच में क्रमबद्ध अनुक्रम की आवश्यकता होती है (अनुक्रम लंबाई के अवरोही क्रम में)। नीचे दिए गए उदाहरण में, अनुक्रम बैच को पहले ही कम अव्यवस्था के लिए हल किया गया था। यात्रा इस सार लिंक पूर्ण कार्यान्वयन के लिए।

सबसे पहले, हम नीचे के रूप में अलग-अलग अनुक्रम लंबाई के 2 अनुक्रमों का एक बैच बनाते हैं। हमारे पास पूरी तरह से बैच में 7 तत्व हैं।

  • प्रत्येक अनुक्रम में 2 का एम्बेडिंग आकार है।
  • पहले अनुक्रम की लंबाई है: 5
  • दूसरे अनुक्रम की लंबाई है: 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]

हम seq_batch5 के बराबर लंबाई (बैच में अधिकतम लंबाई) के साथ दृश्यों के बैच प्राप्त करने के लिए पैड करते हैं। अब, नए बैच में पूरी तरह से 10 तत्व हैं।

# pad the seq_batch
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]]])
"""

फिर, हम पैक करते हैं padded_seq_batch। यह दो टेनर्स का टपल देता है:

  • अनुक्रम बैच में सभी तत्वों सहित पहला डेटा है।
  • दूसरा वह है batch_sizesजो बताएगा कि चरणों द्वारा एक दूसरे से संबंधित तत्व कैसे हैं।
# pack the padded_seq_batch
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]))
"""

अब, हम packed_seq_batchट्यून को आरएनटीएन, एलएसटीएम जैसे पाइटोरेक में आवर्तक मॉड्यूल में पास करते हैं । इसके लिए केवल 5 + 2=7पुनरावर्ती मॉड्यूल में संगणना की आवश्यकता होती है ।

lstm = nn.LSTM(input_size=2, hidden_size=3, batch_first=True)
output, (hn, cn) = lstm(packed_seq_batch.float()) # pass float tensor instead long tensor.
"""
>>> 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>)))
"""

हमें outputआउटपुट के गद्देदार बैच में बदलने की आवश्यकता है :

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])
"""

इस प्रयास की तुलना मानक तरीके से करें

  1. मानक तरीका में, हम केवल उत्तीर्ण करने की आवश्यकता padded_seq_batchकरने के लिए lstmमॉड्यूल। हालाँकि, इसके लिए 10 संगणनाओं की आवश्यकता होती है। इसमें पैडिंग तत्वों पर कई गणना शामिल हैं जो कम्प्यूटेशनल रूप से अक्षम होंगे।

  2. ध्यान दें कि यह गलत अभ्यावेदन का नेतृत्व नहीं करता है , लेकिन सही अभ्यावेदन निकालने के लिए और अधिक तर्क की आवश्यकता है।

    • केवल आगे की दिशा के साथ LSTM (या किसी भी आवर्तक मॉड्यूल) के लिए, यदि हम किसी अनुक्रम के लिए प्रतिनिधित्व के रूप में अंतिम चरण के छिपे हुए वेक्टर को निकालना चाहते हैं, तो हमें T (th) चरण से छिपे हुए वैक्टर को चुनना होगा, जहां T इनपुट की लंबाई है। अंतिम प्रतिनिधित्व को उठाना गलत होगा। ध्यान दें कि बैच में अलग-अलग इनपुट के लिए T अलग होगा।
    • द्वि-दिशात्मक LSTM (या किसी भी आवर्तक मॉड्यूल) के लिए, यह और भी अधिक बोझिल है, क्योंकि एक को दो आरएनएन मॉड्यूल बनाए रखना होगा, एक वह जो इनपुट की शुरुआत में पैडिंग के साथ और एक इनपुट के अंत में पैडिंग के साथ काम करता है, और अंत में ऊपर बताए अनुसार छिपे हुए वैक्टर को निकालना और समेटना।

आइए देखें अंतर:

# The standard approach: using padding batch for recurrent modules
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 >))
"""

उपरोक्त परिणाम बताते हैं कि hn, cnदो तरीकों outputसे अलग हैं जबकि दो तरीकों से पैडिंग तत्वों के लिए अलग-अलग मूल्य हैं।


2
अच्छा उत्तर! बस एक सुधार यदि आप पैडिंग करते हैं तो आपको इनपुट की लंबाई के बराबर इंडेक्स में अंतिम एच का उपयोग नहीं करना चाहिए। इसके अलावा, द्विदिश आरएनएन करने के लिए आप दो अलग-अलग आरएनएन का उपयोग करना चाहेंगे --- एक सामने की गद्दी के साथ और दूसरा सही परिणाम प्राप्त करने के लिए पीठ पर पैडिंग के साथ। अंतिम आउटपुट को पैडिंग और पिक करना "गलत" है। तो आपके तर्क कि यह गलत प्रतिनिधित्व की ओर जाता है गलत है। पैडिंग के साथ समस्या सही है लेकिन अक्षम है (यदि पैक्ड अनुक्रम विकल्प है) और बोझिल हो सकता है (उदाहरण के लिए: द्वि-डेयर आरएनएन)
उमंग गुप्ता

18

उमंग के जवाब में जोड़ते हुए, मुझे यह नोट करना महत्वपूर्ण लगा।

लौटे हुए टपल में पहला आइटम pack_padded_sequenceएक डेटा (टेंसर) है - टैनकोर जिसमें पैक्ड अनुक्रम होता है। दूसरा आइटम प्रत्येक अनुक्रम चरण में बैच आकार के बारे में जानकारी रखने वाले पूर्णांकों का दशांश है।

यहां क्या महत्वपूर्ण है, हालांकि दूसरा आइटम (बैच आकार) बैच में प्रत्येक अनुक्रम चरण पर तत्वों की संख्या का प्रतिनिधित्व करता है, न कि अलग-अलग अनुक्रम लंबाई pack_padded_sequence

उदाहरण के लिए, दिया गया डेटा abcऔर x : वर्ग: के साथ PackedSequenceडेटा शामिल होगा ।axbcbatch_sizes=[2,1,1]


1
धन्यवाद, मैं पूरी तरह से भूल गया था कि। और मेरे उत्तर में एक गलती की है कि अद्यतन करने के लिए जा रहा है। हालाँकि, मैंने दूसरे अनुक्रम को देखा क्योंकि कुछ डेटा को दृश्यों को पुनर्प्राप्त करने के लिए आवश्यक था और यही कारण है कि मेरे विवरण को गड़बड़ कर दिया
उमंग गुप्ता

3

मैंने निम्नानुसार पैक गद्देदार अनुक्रम का उपयोग किया।

packed_embedded = nn.utils.rnn.pack_padded_sequence(seq, text_lengths)
packed_output, hidden = self.rnn(packed_embedded)

जहां text_lengths पैडिंग से पहले व्यक्तिगत अनुक्रम की लंबाई होती है और किसी दिए गए बैच के भीतर लंबाई के घटते क्रम के अनुसार अनुक्रम को क्रमबद्ध किया जाता है।

आप यहां एक उदाहरण देख सकते हैं

और हम पैकिंग करते हैं ताकि RNN अनुक्रम को संसाधित करते समय अवांछित गद्देदार सूचकांक को न देख सके जो समग्र प्रदर्शन को प्रभावित करेगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.