321 lines
16 KiB
Markdown
321 lines
16 KiB
Markdown
|
|
<!--Copyright 2022 The HuggingFace Team. All rights reserved.
|
||
|
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||
|
|
the License. You may obtain a copy of the License at
|
||
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||
|
|
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||
|
|
specific language governing permissions and limitations under the License.
|
||
|
|
⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be
|
||
|
|
rendered properly in your Markdown viewer.
|
||
|
|
-->
|
||
|
|
|
||
|
|
# نمذجة اللغة المقنعة (Masked language modeling)
|
||
|
|
|
||
|
|
[[open-in-colab]]
|
||
|
|
|
||
|
|
<Youtube id="mqElG5QJWUg"/>
|
||
|
|
|
||
|
|
تتنبأ نمذجة اللغة المقنعة برمز مقنع في تسلسل، ويمكن للنموذج الانتباه إلى الرموز بشكل ثنائي الاتجاه. هذا
|
||
|
|
يعني أن النموذج لديه إمكانية الوصول الكاملة إلى الرموز الموجودة على اليسار واليمين. تعد نمذجة اللغة المقنعة ممتازة للمهام التي
|
||
|
|
تتطلب فهمًا سياقيًا جيدًا لتسلسل كامل. BERT هو مثال على نموذج لغة مقنع.
|
||
|
|
|
||
|
|
سيوضح لك هذا الدليل كيفية:
|
||
|
|
|
||
|
|
1. تكييف [DistilRoBERTa](https://huggingface.co/distilbert/distilroberta-base) على مجموعة فرعية [r/askscience](https://www.reddit.com/r/askscience/) من مجموعة بيانات [ELI5](https://huggingface.co/datasets/eli5).
|
||
|
|
2. استخدام نموذج المدرب الخاص بك للاستدلال.
|
||
|
|
|
||
|
|
<Tip>
|
||
|
|
|
||
|
|
لمعرفة جميع البنى والنسخ المتوافقة مع هذه المهمة، نوصي بالتحقق من [صفحة المهمة](https://huggingface.co/tasks/fill-mask)
|
||
|
|
|
||
|
|
</Tip>
|
||
|
|
|
||
|
|
قبل أن تبدأ، تأكد من تثبيت جميع المكتبات الضرورية:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pip install transformers datasets evaluate
|
||
|
|
```
|
||
|
|
|
||
|
|
نحن نشجعك على تسجيل الدخول إلى حساب Hugging Face الخاص بك حتى تتمكن من تحميل ومشاركة نموذجك مع المجتمع. عندما تتم مطالبتك، أدخل رمزك لتسجيل الدخول:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from huggingface_hub import notebook_login
|
||
|
|
|
||
|
|
>>> notebook_login()
|
||
|
|
```
|
||
|
|
|
||
|
|
## تحميل مجموعة بيانات ELI5
|
||
|
|
|
||
|
|
ابدأ بتحميل أول 5000 مثال من مجموعة بيانات [ELI5-Category](https://huggingface.co/datasets/eli5_category) باستخدام مكتبة 🤗 Datasets. سيعطيك هذا فرصة للتجربة والتأكد من أن كل شيء يعمل قبل قضاء المزيد من الوقت في التدريب على مجموعة البيانات الكاملة.
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from datasets import load_dataset
|
||
|
|
|
||
|
|
>>> eli5 = load_dataset("eli5_category", split="train[:5000]")
|
||
|
|
```
|
||
|
|
|
||
|
|
قم بتقسيم مجموعة البيانات `train` إلى مجموعتي تدريب واختبار باستخدام الدالة [`~datasets.Dataset.train_test_split`]:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> eli5 = eli5.train_test_split(test_size=0.2)
|
||
|
|
```
|
||
|
|
|
||
|
|
ثم ألق نظرة على مثال:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> eli5["train"][0]
|
||
|
|
{'q_id': '7h191n',
|
||
|
|
'title': 'What does the tax bill that was passed today mean? How will it affect Americans in each tax bracket?',
|
||
|
|
'selftext': '',
|
||
|
|
'category': 'Economics',
|
||
|
|
'subreddit': 'explainlikeimfive',
|
||
|
|
'answers': {'a_id': ['dqnds8l', 'dqnd1jl', 'dqng3i1', 'dqnku5x'],
|
||
|
|
'text': ["The tax bill is 500 pages long and there were a lot of changes still going on right to the end. It's not just an adjustment to the income tax brackets, it's a whole bunch of changes. As such there is no good answer to your question. The big take aways are: - Big reduction in corporate income tax rate will make large companies very happy. - Pass through rate change will make certain styles of business (law firms, hedge funds) extremely happy - Income tax changes are moderate, and are set to expire (though it's the kind of thing that might just always get re-applied without being made permanent) - People in high tax states (California, New York) lose out, and many of them will end up with their taxes raised.",
|
||
|
|
'None yet. It has to be reconciled with a vastly different house bill and then passed again.',
|
||
|
|
'Also: does this apply to 2017 taxes? Or does it start with 2018 taxes?',
|
||
|
|
'This article explains both the House and senate bills, including the proposed changes to your income taxes based on your income level. URL_0'],
|
||
|
|
'score': [21, 19, 5, 3],
|
||
|
|
'text_urls': [[],
|
||
|
|
[],
|
||
|
|
[],
|
||
|
|
['https://www.investopedia.com/news/trumps-tax-reform-what-can-be-done/']]},
|
||
|
|
'title_urls': ['url'],
|
||
|
|
'selftext_urls': ['url']}
|
||
|
|
```
|
||
|
|
|
||
|
|
على الرغم من أن هذا قد يبدو كثيرًا، إلا أنك مهتم حقًا بحقل `text`. ما هو رائع حول مهام نمذجة اللغة هو أنك لا تحتاج إلى تسميات (تُعرف أيضًا باسم المهمة غير الخاضعة للإشراف) لأن الكلمة التالية *هي* التسمية.
|
||
|
|
|
||
|
|
## معالجة مسبقة (Preprocess)
|
||
|
|
|
||
|
|
<Youtube id="8PmhEIXhBvI"/>
|
||
|
|
|
||
|
|
بالنسبة لنمذجة اللغة المقنعة، فإن الخطوة التالية هي تحميل معالج DistilRoBERTa لمعالجة حقل `text` الفرعي:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from transformers import AutoTokenizer
|
||
|
|
|
||
|
|
>>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilroberta-base")
|
||
|
|
```
|
||
|
|
|
||
|
|
ستلاحظ من المثال أعلاه، أن حقل `text` موجود بالفعل داخل `answers`. هذا يعني أنك ستحتاج إلى استخراج حقل `text` الفرعي من بنيته المضمنة باستخدام الدالة [`flatten`](https://huggingface.co/docs/datasets/process#flatten):
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> eli5 = eli5.flatten()
|
||
|
|
>>> eli5["train"][0]
|
||
|
|
{'q_id': '7h191n',
|
||
|
|
'title': 'What does the tax bill that was passed today mean? How will it affect Americans in each tax bracket?',
|
||
|
|
'selftext': '',
|
||
|
|
'category': 'Economics',
|
||
|
|
'subreddit': 'explainlikeimfive',
|
||
|
|
'answers.a_id': ['dqnds8l', 'dqnd1jl', 'dqng3i1', 'dqnku5x'],
|
||
|
|
'answers.text': ["The tax bill is 500 pages long and there were a lot of changes still going on right to the end. It's not just an adjustment to the income tax brackets, it's a whole bunch of changes. As such there is no good answer to your question. The big take aways are: - Big reduction in corporate income tax rate will make large companies very happy. - Pass through rate change will make certain styles of business (law firms, hedge funds) extremely happy - Income tax changes are moderate, and are set to expire (though it's the kind of thing that might just always get re-applied without being made permanent) - People in high tax states (California, New York) lose out, and many of them will end up with their taxes raised.",
|
||
|
|
'None yet. It has to be reconciled with a vastly different house bill and then passed again.',
|
||
|
|
'Also: does this apply to 2017 taxes? Or does it start with 2018 taxes?',
|
||
|
|
'This article explains both the House and senate bills, including the proposed changes to your income taxes based on your income level. URL_0'],
|
||
|
|
'answers.score': [21, 19, 5, 3],
|
||
|
|
'answers.text_urls': [[],
|
||
|
|
[],
|
||
|
|
[],
|
||
|
|
['https://www.investopedia.com/news/trumps-tax-reform-what-can-be-done/']],
|
||
|
|
'title_urls': ['url'],
|
||
|
|
'selftext_urls': ['url']}
|
||
|
|
```
|
||
|
|
|
||
|
|
كل حقل فرعي هو الآن عمود منفصل كما هو موضح بواسطة بادئة `answers`، وحقل `text` هو قائمة الآن. بدلاً من
|
||
|
|
معالجة كل جملة بشكل منفصل، قم بتحويل القائمة إلى سلسلة حتى تتمكن من معالجتها بشكل مشترك.
|
||
|
|
|
||
|
|
هنا أول دالة معالجة مسبقة لربط قائمة السلاسل لكل مثال ومعالجة النتيجة:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> def preprocess_function(examples):
|
||
|
|
... return tokenizer([" ".join(x) for x in examples["answers.text"]])
|
||
|
|
```
|
||
|
|
|
||
|
|
لتطبيق دالة المعالجة المسبقة على مجموعة البيانات بأكملها، استخدم الدالة 🤗 Datasets [`~datasets.Dataset.map`]. يمكنك تسريع دالة `map` عن طريق تعيين `batched=True` لمعالجة عدة عناصر في وقت واحد، وزيادة عدد العمليات باستخدام `num_proc`. احذف أي أعمدة غير ضرورية:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> tokenized_eli5 = eli5.map(
|
||
|
|
... preprocess_function,
|
||
|
|
... batched=True,
|
||
|
|
... num_proc=4,
|
||
|
|
... remove_columns=eli5["train"].column_names,
|
||
|
|
... )
|
||
|
|
```
|
||
|
|
|
||
|
|
|
||
|
|
تحتوي مجموعة البيانات هذه على تسلسلات رمزية، ولكن بعضها أطول من الطول الأقصى للمدخلات للنموذج.
|
||
|
|
|
||
|
|
يمكنك الآن استخدام دالة معالجة مسبقة ثانية لـ:
|
||
|
|
- تجميع جميع التسلسلات
|
||
|
|
- تقسيم التسلسلات المجمّعة إلى أجزاء أقصر محددة بـ `block_size`، والتي يجب أن تكون أقصر من الحد الأقصى لطول المدخلات ومناسبة لذاكرة GPU.
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> block_size = 128
|
||
|
|
|
||
|
|
>>> def group_texts(examples):
|
||
|
|
... # تجميع جميع النصوص.
|
||
|
|
... concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
|
||
|
|
... total_length = len(concatenated_examples[list(examples.keys())[0]])
|
||
|
|
... # نتجاهل الجزء المتبقي الصغير، يمكننا إضافة الحشو إذا كان النموذج يدعمه بدلاً من هذا الإسقاط، يمكنك
|
||
|
|
... # تخصيص هذا الجزء حسب احتياجاتك.
|
||
|
|
... if total_length >= block_size:
|
||
|
|
... total_length = (total_length // block_size) * block_size
|
||
|
|
... # تقسيمها إلى أجزاء بحجم block_size.
|
||
|
|
... result = {
|
||
|
|
... k: [t[i : i + block_size] for i in range(0, total_length, block_size)]
|
||
|
|
... for k, t in concatenated_examples.items()
|
||
|
|
... }
|
||
|
|
... return result
|
||
|
|
```
|
||
|
|
|
||
|
|
طبق دالة `group_texts` على مجموعة البيانات بأكملها:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> lm_dataset = tokenized_eli5.map(group_texts, batched=True, num_proc=4)
|
||
|
|
```
|
||
|
|
|
||
|
|
الآن، قم بإنشاء دفعة من الأمثلة باستخدام [`DataCollatorForLanguageModeling`]. من الأكثر كفاءة أن تقوم بـ *الحشو الديناميكي* ليصل طولها إلى أطول جملة في الدفعة أثناء التجميع، بدلاً من حشو مجموعة البيانات بأكملها إلى الطول الأقصى.
|
||
|
|
|
||
|
|
|
||
|
|
استخدم رمز نهاية التسلسل كرمز الحشو وحدد `mlm_probability` لحجب الرموز عشوائياً كل مرة تكرر فيها البيانات:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from transformers import DataCollatorForLanguageModeling
|
||
|
|
|
||
|
|
>>> tokenizer.pad_token = tokenizer.eos_token
|
||
|
|
>>> data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)
|
||
|
|
```
|
||
|
|
|
||
|
|
## التدريب (Train)
|
||
|
|
|
||
|
|
|
||
|
|
<Tip>
|
||
|
|
|
||
|
|
إذا لم تكن على دراية بتعديل نموذج باستخدام [`Trainer`], ألق نظرة على الدليل الأساسي [هنا](../training#train-with-pytorch-trainer)!
|
||
|
|
|
||
|
|
</Tip>
|
||
|
|
|
||
|
|
أنت مستعد الآن لبدء تدريب نموذجك! قم بتحميل DistilRoBERTa باستخدام [`AutoModelForMaskedLM`]:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from transformers import AutoModelForMaskedLM
|
||
|
|
|
||
|
|
>>> model = AutoModelForMaskedLM.from_pretrained("distilbert/distilroberta-base")
|
||
|
|
```
|
||
|
|
|
||
|
|
في هذه المرحلة، تبقى ثلاث خطوات فقط:
|
||
|
|
|
||
|
|
1. حدد معلمات التدريب الخاصة بك في [`TrainingArguments`]. المعلمة الوحيدة المطلوبة هي `output_dir` والتي تحدد مكان حفظ نموذجك. ستقوم بدفع هذا النموذج إلى Hub عن طريق تعيين `push_to_hub=True` (يجب أن تكون مسجلاً الدخول إلى Hugging Face لتحميل نموذجك).
|
||
|
|
2. قم بتمرير معلمات التدريب إلى [`Trainer`] مع النموذج، ومجموعات البيانات، ومجمّع البيانات.
|
||
|
|
3. قم باستدعاء [`~Trainer.train`] لتعديل نموذجك.
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> training_args = TrainingArguments(
|
||
|
|
... output_dir="my_awesome_eli5_mlm_model",
|
||
|
|
... eval_strategy="epoch",
|
||
|
|
... learning_rate=2e-5,
|
||
|
|
... num_train_epochs=3,
|
||
|
|
... weight_decay=0.01,
|
||
|
|
... push_to_hub=True,
|
||
|
|
... )
|
||
|
|
|
||
|
|
>>> trainer = Trainer(
|
||
|
|
... model=model,
|
||
|
|
... args=training_args,
|
||
|
|
... train_dataset=lm_dataset["train"],
|
||
|
|
... eval_dataset=lm_dataset["test"],
|
||
|
|
... data_collator=data_collator,
|
||
|
|
... tokenizer=tokenizer,
|
||
|
|
... )
|
||
|
|
|
||
|
|
>>> trainer.train()
|
||
|
|
```
|
||
|
|
|
||
|
|
بمجرد اكتمال التدريب، استخدم طريقة [`~transformers.Trainer.evaluate`] لتقييم النموذج والحصول على مقياس
|
||
|
|
الحيرة:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> import math
|
||
|
|
|
||
|
|
>>> eval_results = trainer.evaluate()
|
||
|
|
>>> print(f"Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
|
||
|
|
Perplexity: 8.76
|
||
|
|
```
|
||
|
|
|
||
|
|
ثم شارك نموذجك على Hub باستخدام طريقة [`~transformers.Trainer.push_to_hub`] حتى يتمكن الجميع من استخدام نموذجك:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> trainer.push_to_hub()
|
||
|
|
```
|
||
|
|
|
||
|
|
<Tip>
|
||
|
|
|
||
|
|
لمثال أكثر تفصيلاً حول كيفية تعديل نموذج للنمذجة اللغوية المقنعة، ألق نظرة على الدفتر المقابل
|
||
|
|
[دفتر PyTorch](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/language_modeling.ipynb)
|
||
|
|
أو [دفتر TensorFlow](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/language_modeling-tf.ipynb).
|
||
|
|
|
||
|
|
</Tip>
|
||
|
|
|
||
|
|
## الاستدلال
|
||
|
|
|
||
|
|
رائع، الآن بعد أن قمت بتعديل نموذج، يمكنك استخدامه للاستدلال!
|
||
|
|
|
||
|
|
جهّز بعض النصوص التي تريد أن يملأ النموذج الفراغات فيها، واستخدم الرمز الخاص `<mask>` للإشارة إلى الفراغ:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> text = "The Milky Way is a <mask> galaxy."
|
||
|
|
```
|
||
|
|
|
||
|
|
أبسط طريقة لتجربة نموذجك المعدل للاستدلال هي استخدامه في [`pipeline`]. قم بإنشاء كائن `pipeline` لملء الفراغ مع نموذجك، ومرر نصك إليه. إذا أردت، يمكنك استخدام معلمة `top_k` لتحديد عدد التنبؤات التي تريد إرجاعها:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from transformers import pipeline
|
||
|
|
|
||
|
|
>>> mask_filler = pipeline("fill-mask", "username/my_awesome_eli5_mlm_model")
|
||
|
|
>>> mask_filler(text, top_k=3)
|
||
|
|
[{'score': 0.5150994658470154,
|
||
|
|
'token': 21300,
|
||
|
|
'token_str': ' spiral',
|
||
|
|
'sequence': 'The Milky Way is a spiral galaxy.'},
|
||
|
|
{'score': 0.07087188959121704,
|
||
|
|
'token': 2232,
|
||
|
|
'token_str': ' massive',
|
||
|
|
'sequence': 'The Milky Way is a massive galaxy.'},
|
||
|
|
{'score': 0.06434620916843414,
|
||
|
|
'token': 650,
|
||
|
|
'token_str': ' small',
|
||
|
|
'sequence': 'The Milky Way is a small galaxy.'}]
|
||
|
|
```
|
||
|
|
|
||
|
|
قم بتجزئة النص وإرجاع `input_ids` كمتجهات PyTorch. ستحتاج أيضًا إلى تحديد موضع رمز `<mask>`:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from transformers import AutoTokenizer
|
||
|
|
|
||
|
|
>>> tokenizer = AutoTokenizer.from_pretrained("username/my_awesome_eli5_mlm_model")
|
||
|
|
>>> inputs = tokenizer(text, return_tensors="pt")
|
||
|
|
>>> mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
|
||
|
|
```
|
||
|
|
|
||
|
|
قم بتمرير المدخلات إلى النموذج وإرجاع `logits` للرمز المقنع:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> from transformers import AutoModelForMaskedLM
|
||
|
|
|
||
|
|
>>> model = AutoModelForMaskedLM.from_pretrained("username/my_awesome_eli5_mlm_model")
|
||
|
|
>>> logits = model(**inputs).logits
|
||
|
|
>>> mask_token_logits = logits[0, mask_token_index, :]
|
||
|
|
```
|
||
|
|
|
||
|
|
ثم قم بإرجاع الرموز الثلاثة المقنعة ذات الاحتمالية الأعلى وطباعتها:
|
||
|
|
|
||
|
|
```py
|
||
|
|
>>> top_3_tokens = torch.topk(mask_token_logits, 3, dim=1).indices[0].tolist()
|
||
|
|
|
||
|
|
>>> for token in top_3_tokens:
|
||
|
|
... print(text.replace(tokenizer.mask_token, tokenizer.decode([token])))
|
||
|
|
The Milky Way is a spiral galaxy.
|
||
|
|
The Milky Way is a massive galaxy.
|
||
|
|
The Milky Way is a small galaxy.
|
||
|
|
```
|