Pytorch
TensorFlow

#
डेटा संसाधित करना

पिछले अध्याय के उदाहरण को जारी रखते हुए, यहां बताया गया है कि हम PyTorch में एक बैच पर अनुक्रम वर्गीकारक को कैसे प्रशिक्षित करेंगे:

    import torch
    from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification

    # Same as before
    checkpoint = "bert-base-uncased"
    tokenizer = AutoTokenizer.from_pretrained(checkpoint)
    model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
    sequences = [
        "I've been waiting for a HuggingFace course my whole life.",
        "This course is amazing!",
    ]
    batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

    # This is new
    batch["labels"] = torch.tensor([1, 1])

    optimizer = AdamW(model.parameters())
    loss = model(**batch).loss
    loss.backward()
    optimizer.step()

बेशक, केवल दो वाक्यों पर मॉडल को प्रशिक्षित करने से बहुत अच्छे परिणाम नहीं मिलेंगे। बेहतर परिणाम प्राप्त करने के लिए, आपको एक बड़ा डेटासेट तैयार करना होगा।

इस खंड में हम एक उदाहरण के रूप में MRPC (Microsoft Research Paraphrase Corpus) डेटासेट का उपयोग करेंगे, जिसे विलियम बी. डोलन और क्रिस ब्रोकेट द्वारा एक पेपर में पेश किया गया था। डेटासेट में 5,801 वाक्यों के जोड़े हैं, साथ मे एक लेबल जो दर्शाता है कि वे पैराफ्रेज हैं या नहीं (यानी, क्या दोनों वाक्यों का मतलब एक ही है)। हमने इसे इस अध्याय के लिए चुना है क्योंकि यह एक छोटा डेटासेट है, इसलिए इस पर प्रशिक्षण के साथ प्रयोग करना आसान है।

###
हब से डेटासेट लोड करना

हब में केवल मॉडल ही नहीं हैं; इसमें कई अलग-अलग भाषाओं में कई डेटासेट भी हैं। आप यहां डेटासेट ब्राउज़ कर सकते हैं, और हम अनुशंसा करते हैं कि आप इस अनुभाग को पढ़ने के बाद एक नए डेटासेट को लोड और संसाधित करने का प्रयास करें (यहां सामान्य दस्तावेज देखें)। लेकिन अभी के लिए, आइए MRPC डेटासेट पर ध्यान दें! यह GLUE बेंचमार्क की रचना करने वाले 10 डेटासेट में से एक है, जो एक अकादमिक बेंचमार्क है जिसका उपयोग 10 अलग-अलग पाठ वर्गीकरण कार्यों में ML मॉडल के प्रदर्शन को मापने के लिए किया जाता है।

🤗 डेटासेट लाइब्रेरी एक बहुत ही सरल कमांड प्रदान करती है हब पर डेटासेट को डाउनलोड और कैश करने के लिए। हम MRPC डेटासेट को इस तरह डाउनलोड कर सकते हैं:

    from datasets import load_dataset

    raw_datasets = load_dataset("glue", "mrpc")
    raw_datasets

    DatasetDict({
        train: Dataset({
            features: ['sentence1', 'sentence2', 'label', 'idx'],
            num_rows: 3668
        })
        validation: Dataset({
            features: ['sentence1', 'sentence2', 'label', 'idx'],
            num_rows: 408
        })
        test: Dataset({
            features: ['sentence1', 'sentence2', 'label', 'idx'],
            num_rows: 1725
        })
    })

जैसा कि आप देख सकते हैं, हमें एक `DatasetDict` वस्तु मिला जिसमें प्रशिक्षण सेट, सत्यापन सेट और परीक्षण सेट है। उनमें से प्रत्येक में कई कॉलम (`sentence1`, `sentence2`, `label`, और `idx`) और एक चर पंक्तियों की संख्या, जो प्रत्येक सेट में तत्वों की संख्या है (तो, वाक्यों के 3,668 जोड़े प्रशिक्षण सेट में, 408 सत्यापन सेट में, और परीक्षण सेट में 1,725 है)।

यह कमांड डेटासेट को डाउनलोड और कैश करता हैं, जो डिफ़ॉल्ट रूप से इस जगह मे ~/.cache/huggingface/dataset जाता हैं। अध्याय 2 से याद करें कि आप `HF_HOME` पर्यावरण चर सेट करके अपने कैशे फ़ोल्डर को अनुकूलित कर जगह बदल सकते हैं।

हम अपने `raw_datasets` वस्तु में वाक्यों की प्रत्येक जोड़ी को अनुक्रमणित करके अभिगम कर सकते हैं, जैसे किसी शब्दकोश के साथ:

    raw_train_dataset = raw_datasets["train"]
    raw_train_dataset[0]

    {'idx': 0,
     'label': 1,
     'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
     'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}

हम देख सकते हैं कि लेबल पहले से ही पूर्णांक हैं, इसलिए हमें वहां कोई पूर्व प्रसंस्करण नहीं करना होगा। यह जानने के लिए कि कौन सा पूर्णांक किस लेबल से मेल खाता है, हम अपने `raw_train_dataset` की `features` का निरीक्षण कर सकते हैं। यह हमें प्रत्येक कॉलम का प्रकार बताएगा:

    raw_train_dataset.features

    {'sentence1': Value(dtype='string', id=None),
     'sentence2': Value(dtype='string', id=None),
     'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
     'idx': Value(dtype='int32', id=None)}

परदे के पीछे, `label` प्रकार `ClassLabel` का है, और पूर्णांक का लेबल नाम से मानचित्रण names फ़ोल्डर में संग्रहित किया जाता है। `0` मेल खाता है `not_equivalent` से, और `1` मेल खाता है `equivalent` से।

✏️ कोशिश करके देखे! प्रशिक्षण सेट के तत्व 15 और सत्यापन सेट के तत्व 87 को देखें। उनके लेबल क्या हैं?

###
डेटासेट का पूर्वप्रक्रमण करना

डेटासेट को पूर्व संसाधित करने के लिए, हमें टेक्स्ट को उन नंबरों में बदलने की जरूरत है, जिन्हें मॉडल समझ सकता है। जैसा कि आपने पिछले अध्याय में देखा, यह एक टोकननाइज़र के साथ किया जाता है। हम टोकननाइज़र मे एक वाक्य या वाक्यों की एक सूची डाल सकते हैं, ताकि हम सीधे सभी पहले वाक्यों और सभी दूसरे वाक्यों की प्रत्येक जोड़ी को टोकननाइज कर सके इस तरह से :

    from transformers import AutoTokenizer

    checkpoint = "bert-base-uncased"
    tokenizer = AutoTokenizer.from_pretrained(checkpoint)
    tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
    tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])

हालाँकि, हम केवल दो अनुक्रमों को मॉडल में पारित नहीं कर सकते और प्रिडिक्शन कर सकते कि दो वाक्य पैराफ्रेश हैं या नहीं। हमें दो अनुक्रमों को एक जोड़ी के रूप में संभालने की जरूरत है, और उपयुक्त पूर्व प्रसंस्करण लागू करना है। सौभाग्य से, टोकननाइज़र अनुक्रमों की जोड़ी भी ले सकता है और इसे हमारे BERT मॉडल की अपेक्षा के अनुसार तैयार कर सकता है:

    inputs = tokenizer("This is the first sentence.", "This is the second one.")
    inputs

    {
      'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
      'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
      'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    }

हमने अध्याय 2 में `input_ids` और `attention_mask` कुंजियों पर चर्चा की, लेकिन हमने `token_type_ids` के बारे में बात नहीं की। इस उदाहरण में, यह कुंजी मॉडल को बताता है कि इनपुट का कौन सा हिस्सा पहला वाक्य है और कौन सा दूसरा वाक्य है।

✏️ कोशिश करके देखे! प्रशिक्षण सेट के तत्व 15 को लें और टोकननाइज करें दो वाक्यों को अलग-अलग और एक जोड़ी के रूप में। दोनों परिणामों में क्या अंतर है?

यदि हम `input_ids` के अंदर IDs को शब्दों में वापस व्याख्या करते हैं:

    tokenizer.convert_ids_to_tokens(inputs["input_ids"])

हमें मिलेगा:

    ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']

तो हम देख सकते हैं कि मॉडल अपेक्षा करता है कि इनपुट का फॉर्म `[CLS] sentence1 [SEP] sentence2 [SEP]` का होगा जब दो वाक्य हों। इसे `token_type_ids` के साथ संरेखित करने से हमें यह मिलता है:

    ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
    [      0,      0,    0,     0,       0,          0,   0,       0,      1,    1,     1,        1,     1,   1,       1]

जैसा कि आप देख सकते हैं, इनपुट के जो हिस्से `[CLS] sentence1 [SEP]` के अनुरूप है, उन सभी के पास टोकन टाइप आईडी है `0`, जबकि अन्य हिस्से, जो `sentence2 [SEP]` के अनुरूप है, सभी के पास एक टोकन टाइप आईडी है `1`।

ध्यान दें कि यदि आप एक अलग चेकपॉइंट का चयन करते हैं, तो जरूरी नहीं कि आपके टोकननाइज इनपुट में `token_type_ids` हों (उदाहरण के लिए, यदि आप DistilBERT मॉडल का उपयोग करते हैं तो वे वापस नहीं आते हैं)। उन्हें केवल तभी लौटाया जाता है जब मॉडल को पता चल जाएगा कि उनके साथ क्या करना है, क्योंकि इसने उन्हें अपने पूर्व प्रशिक्षण के दौरान देखा है।

यहां, BERT को टोकन टाइप आईडी के साथ पूर्व प्रशिक्षित किया गया है, और नकाबपोश भाषा मॉडलिंग का उद्देश्य जिसकी हमने अध्याय 1 में बात की थी के शीर्ष पर, इसका एक अतिरिक्त उद्देश्य है जिसे अगले वाक्य पूर्वानुमान कहा जाता है। इस कार्य का लक्ष्य वाक्यों के जोड़े के बीच संबंध को मॉडल करना है।

अगले वाक्य पूर्वानुमान के साथ, मॉडल को वाक्यों के जोड़े (बेतरतीब ढंग से नकाबपोश टोकन के साथ) प्रदान किए जाते हैं और पूछा जाता है कि पूर्वानुमान लगाओ कि क्या दूसरा वाक्य पहले का अनुसरण करता है। कार्य को गैर-तुच्छ बनाने के लिए, आधे समय में वाक्य एक-दूसरे का अनुसरण करते हैं मूल दस्तावेज़ में, और दूसरे आधे समय में दो वाक्य दो अलग-अलग दस्तावेज़ों से आते हैं।

सामान्य तौर पर, आपको इस बारे में चिंता करने की आवश्यकता नहीं है कि आपके टोकननाइज़ड इनपुट में `token_type_ids` हैं या नहीं: जब तक आप टोकननाइज़र और मॉडल के लिए एक ही चेकपॉइंट का उपयोग करते हैं, तब तक सब कुछ ठीक रहेगा क्योंकि टोकननाइज़र जानता है कि उसके मॉडल को क्या प्रदान करना है।

अब जब हमने देखा कि कैसे हमारा टोकननाइज़र वाक्यों की एक जोड़ी से निपटता है, हम इसका उपयोग अपने पूरे डेटासेट को टोकननाइज़ करने के लिए कर सकते हैं: पिछले अध्याय की तरह, हम टोकननाइज़र को पहले वाक्यों की सूची, फिर दूसरे वाक्यों की सूची देकर वाक्यों के जोड़े की सूची खिला सकते है। यह पैडिंग और ट्रंकेशन विकल्पों के साथ भी संगत है जिसे हमने अध्याय 2 में देखा था। इसलिए, प्रशिक्षण डेटासेट को पूर्व प्रसंस्करण करने का एक तरीका है:

    tokenized_dataset = tokenizer(
        raw_datasets["train"]["sentence1"],
        raw_datasets["train"]["sentence2"],
        padding=True,
        truncation=True,
    )

यह अच्छी तरह से काम करता है, लेकिन इसमें एक शब्दकोश (साथ में हमारी कुंजी, `input_ids`, `attention_mask`, और `token_type_ids`, और मान जो सूचियों की सूचियां हैं) के लौटने का नुकसान है। यह केवल तभी काम करेगा जब आपके पास पर्याप्त RAM हो अपने पूरे डेटासेट को टोकननाइजेशन के दौरान स्टोर करने के लिए (जबकि 🤗 डेटासेट लाइब्रेरी के डेटासेट अपाचे एरो फाइलें हैं जो डिस्क पर संग्रहीत है, तो आप केवल उन सैम्पल्स को रखते हैं जिन्हें आप मेमोरी मे लोड करना चाहतें है)।

डेटा को डेटासेट के रूप में रखने के लिए, हम `Dataset.map()` पद्धति का उपयोग करेंगे। अगर हमें सिर्फ टोकननाइजेशन की तुलना में अधिक पूर्व प्रसंस्करण की आवश्यकता होती है, तो यह हमें कुछ अधिक लचीलेपन की भी अनुमति देता है। `map()` विधि डेटासेट के प्रत्येक तत्व पर एक फ़ंक्शन लागू करके काम करती है, तो चलिए एक फ़ंक्शन को परिभाषित करते हैं जो हमारे इनपुट को टोकननाइज़ करेगा :

    def tokenize_function(example):
        return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

यह फ़ंक्शन एक शब्दकोश लेता है (जैसे हमारे डेटासेट के आइटम) और `input_ids`, `attention_mask`, और `token_type_ids` कुंजियों के साथ एक नया शब्दकोश देता है। ध्यान दें कि यह तब भी काम करता है जब `example` शब्दकोश में कई सैम्पल्स हों (प्रत्येक कुंजी वाक्यों की सूची के रूप में) क्योंकि `टोकनाइज़र` वाक्यों के जोड़े की सूची पर काम करता है, जैसा कि पहले देखा गया था। यह हमें हमारे `map()` के कॉल में `batched=True` विकल्प का उपयोग करने की अनुमति देगा, जो टोकनाइजेशन को बहुत तेज करेगा। `टोकनाइज़र` को 🤗 टोकननाइज़रस लाइब्रेरी से टोकननाइज़र जो Rust में लिखा है द्वारा समर्थित किया जाता है। यह टोकननाइज़र बहुत तेज़ हो सकता है, लेकिन केवल तभी जब हम इसे एक साथ ढेर सारे इनपुट दें।

ध्यान दें कि हमने अभी के लिए अपने टोकननाइजेशन फ़ंक्शन में `पैडिंग` आर्गूमेन्ट को छोड़ दिया है। ऐसा इसलिए है क्योंकि सभी सैम्पल्स को अधिकतम लंबाई तक पैडिंग करना कुशल नहीं है: जब हम बैच बना रहे हों तो सैम्पल्स को पैड करना बेहतर होता है, क्योंकि तब हमें केवल उस बैच में अधिकतम लंबाई तक पैड करने की आवश्यकता होती है, और न कि पुरे डेटासेट मे अधिकतम लंबाई तक। यह बहुत समय और प्रसंस्करण शक्ति को बचा सकता है जब इनपुट में बहुत परिवर्तनशील लंबाई होती है!

यहां बताया गया है कि हम अपने सभी डेटासेट पर एक बार में टोकननाइजेशन फ़ंक्शन कैसे लागू करते हैं। हम `batched=True` का उपयोग कर रहे है `map` को कॉल करने के लिए, इसलिए फ़ंक्शन हमारे डेटासेट के कई तत्वों पर एक साथ लागू होता है, न कि प्रत्येक तत्व पर अलग से। यह तेजी से पूर्व प्रसंस्करण की अनुमति देता है।

    tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
    tokenized_datasets

🤗 डेटासेट लाइब्रेरी इस प्रसंस्करण को लागू करने के लिए , डेटासेट में नए फ़ील्ड जोड़ते है, जो प्रीप्रोसेसिंग फ़ंक्शन द्वारा लौटाए गए शब्दकोश में प्रत्येक कुंजी के लिए एक होता है:

    DatasetDict({
        train: Dataset({
            features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
            num_rows: 3668
        })
        validation: Dataset({
            features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
            num_rows: 408
        })
        test: Dataset({
            features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
            num_rows: 1725
        })
    })

आप बहुप्रक्रमण का भी उपयोग कर सकते हैं बस अपने पूर्व प्रसंस्करण फ़ंक्शन को `map()` के साथ लागू करते समय `num_proc` तर्क को पास करना है। हमने यहां ऐसा नहीं किया क्योंकि 🤗 टोकनाइज़रस लाइब्रेरी पहले से ही हमारे सैम्पल्स को तेज़ी से टोकनाइज़ करने के लिए कई थ्रेड्स का उपयोग करती है, लेकिन यदि आप इस लाइब्रेरी द्वारा समर्थित तेज़ टोकनाइज़र का उपयोग नहीं कर रहे हैं, तो यह आपके पूर्व प्रसंस्करण को गति दे सकता है।

हमारा `tokenize_function` एक शब्दकोश `input_ids`, `attention_mask`, और `token_type_ids` कुंजियों के साथ देता है, इसलिए उन तीनो क्षेत्रों को हमारे डेटासेट के सभी विभाजनों में जोड़ दिया जाता है। ध्यान दें कि हम मौजूदा फ़ील्डस को भी बदल सकते थे यदि हमारे प्रीप्रोसेसिंग फ़ंक्शन ने डेटासेट में मौजूदा कुंजी के लिए एक नया मान लौटाया होता, जिस पर हमने `map()` लागू किया।

आखिरी चीज जो हमें करने की आवश्यकता होगी वह है सभी उदाहरणों को सबसे लंबे तत्व की लंबाई तक पैड करना जब हम तत्वों को एक साथ बैच करते हैं — यह एक तकनीक है जिसे हम डायनामिक पैडिंग के रूप में संदर्भित करते हैं।

###
डायनामिक पैडिंग

जो फ़ंक्शन बैच के अंदर सैम्पल्स को एक साथ रखने के लिए जिम्मेदार हो उसे collate function कहा जाता है। यह एक आर्गूमेन्ट है जिसे आप एक `DataLoader` बनाते समय पारित कर सकते हैं, वरना एक ऐसा फ़ंक्शन है जो आपके सैम्पल्स को केवल PyTorch टेंसर में बदल देगा और उन्हें जोड़ देगा (पुनरावर्ती यदि आपके तत्व सूचियां, टुपल्स या शब्दकोश हैं)। हमारे मामले में यह संभव नहीं होगा क्योंकि हमारे पास जो इनपुट हैं वे सभी एक ही आकार के नहीं होंगे। हमने जानबूझकर पैडिंग को स्थगित कर दिया है, केवल इसे प्रत्येक बैच पर आवश्यक रूप से लागू करने के लिए और बहुत अधिक पैडिंग के साथ अधिक लंबे इनपुट से बचने के लिए। यह प्रशिक्षण को काफी तेज कर देगा, लेकिन ध्यान दें कि यदि आप TPU पर प्रशिक्षण कर रहे हैं तो यह समस्या पैदा कर सकता है — TPUs निश्चित आकार पसंद करते हैं, तब भी जब इसके लिए अतिरिक्त पैडिंग की आवश्यकता होती है।

व्यवहार में ऐसा करने के लिए, हमें एक कोलेट फ़ंक्शन को परिभाषित करना होगा जो उस डेटासेट के आइटम पर सही मात्रा में पैडिंग लागू करेगा जिसे हम एक साथ बैच बनाना हैं। सौभाग्य से, 🤗 ट्रान्सफ़ॉर्मर्स लाइब्रेरी हमें `DataCollatorWithPadding` के माध्यम से ऐसा फ़ंक्शन प्रदान करती है। जब आप इसे इन्स्टैन्शीऐट करते हैं तो यह एक टोकननाइज़र लेता है (यह जानने के लिए कि किस पैडिंग टोकन का उपयोग करना है, और क्या मॉडल को पैडिंग के बाईं ओर या इनपुट के दाईं ओर चाहिए) और वह सब कुछ करेगा जो आपको चाहिए:

    from transformers import DataCollatorWithPadding

    data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

इस नए खिलौने का परीक्षण करने के लिए, आइए हमारे प्रशिक्षण सेट से कुछ सैम्पल्स लें जिन्हें हम एक साथ बैच बनाना चाहेंगे। यहां, हम कॉलम `idx`, `sentence1`, और `sentence2` को हटा देते हैं क्योंकि उनकी आवश्यकता नहीं होगी और इसमें स्ट्रिंग्स होंगे (और हम स्ट्रिंग्स के साथ टेंसर नहीं बना सकते) और आइये बैच में प्रत्येक प्रविष्टि की लंबाई पर एक नज़र डाले:

    samples = tokenized_datasets["train"][:8]
    samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
    [len(x) for x in samples["input_ids"]]

    [50, 59, 47, 67, 59, 50, 62, 32]

कोई आश्चर्य नहीं, हमें 32 से 67 तक की अलग-अलग लंबाई के सैम्पल्स मिलते हैं। डायनेमिक पैडिंग का मतलब है कि इस बैच के सभी सैम्पल्स को 67 की लंबाई तक पैड किया जाना चाहिए, जो की सबसे अधिकतम लंबाई है बैच के अंदर की। डायनेमिक पैडिंग के बिना, सभी सैम्पल्स को पूरे डेटासेट में अधिकतम लंबाई तक या मॉडल द्वारा स्वीकार की जा सकने वाली अधिकतम लंबाई तक पैड करना होगा। आइए दोबारा जांचें कि हमारा `data_collator` बैच को डायनेमिकली पैडिंग कर रहा है:

    batch = data_collator(samples)
    {k: v.shape for k, v in batch.items()}

    {'attention_mask': torch.Size([8, 67]),
     'input_ids': torch.Size([8, 67]),
     'token_type_ids': torch.Size([8, 67]),
     'labels': torch.Size([8])}

देखने में सही है! अब जबकि हम देख चुके है की हमारा मॉडल कच्चे टेक्स्ट से बैचस तक निपट सकता है, तो अब हम इसे फ़ाइन ट्यून करने के लिए तैयार हैं!

✏️ कोशिश करके देखे! कोशिश करके देखे! GLUE SST-2 डेटासेट पर प्रीप्रोसेसिंग को दोहराएं। यह थोड़ा अलग है क्योंकि यह जोड़े के बजाय एकल वाक्यों से बना है, लेकिन बाकी जो हमने किया वो वैसा ही दिखना चाहिए। एक कठिन चुनौती के लिए, एक प्रीप्रोसेसिंग फ़ंक्शन लिखने का प्रयास करें जो किसी भी GLUE कार्यों पर काम करता हो।