تحتاج جميع أنواع البيانات مهما كان نوعها سواء كانت نصوصًا أو صورًا أو ملفات صوتية أو غير ذلك إلى معالجة مسبقة أو معالجة تحضيرية قبل تمريرها لنماذج الذكاء الاصطناعي حتى تتدرب عليها، نطلق على هذه العملية اسم "data Preprocessing" إذ تتحول هذه البيانات بعد معالجتها إلى دفعاتٍ من التنسورات tensors التي تمثل بنى ملائمة لتمثيل البيانات تتوافق مع النموذج ويمكنه التعامل معها، وفي هذا المجال توفر مكتبة المحولات Transformers مجموعةً واسعة من أصناف المعالجة تُسّهِل عليك تجهيز بياناتك للنموذج، وهو ما سنتعلمه في مقال اليوم إذ سنُجري معالجة مسبقة لأنواع البيانات التالية:
- النصوص: سنستخدم المُرَمِّزات Tokenizer لمعالجة النص وتحويله إلى سلسلة رموز tokens، ثم تمثيلها عدديًا وتجميعها على هيئة tensors.
- الكلام والصوت: سنعتمد على مستخرج الميزات Feature extractor لاستخراج الميزات المتسلسلة من الأمواج الصوتية وتحويلها إلى tensors.
- الصور: سنتعامل مع معالج الصور ImageProcessor ونُمَرِر له الصور المطلوبة قبل إدخالها للنموذج فيُحولها إلى tensors.
- الأنماط المتعددة Multimodal: نستخدم في هذه الحالة معالجًا Processor يجمع بين وظيفة المُرَمِّز ووظيفة معالج الصور أو بين وظيفة المُرَمِّز ومستخرج الميزات حسب أنماط البيانات المستخدمة في مشروعك وهل هي تتضمن نصًا وصوتًا، أو نصًا وصورة، أو غير ذلك.
ملاحظة: ننصحك باستخدام المعالج التلقائي AutoProcessor فهو يساعدك بصورة كبيرة ويختار لك دومًا دائمًا صنف المعالج المناسب لنموذجك سواء كان مُرَمِّز أو معالج صور أو مستخرج ميزات أو معالج للأنماط المتعددة.
قبل البدء بأمثلتنا العملية سنُثَبِّتْ مجموعة البيانات Datasets
لنتمكن من تحميل مجموعات بيانات تجريبية لعملنا من خلال كتابة التعليمة التالية:
pip install datasets
معالجة اللغة الطبيعية
المُرَمِّز tokenizer هو الأداة الرئيسية لمعالجة النصوص اللغوية قبل تمريرها للنموذج، إذ يُقَسٍّمُها إلى رموز tokens وفقًا لقواعد خاصة، ثم يحوّل هذه الرموز إلى أعداد ثم إلى تنسورات tensors تمثل المدخلات المقبولة للنموذج، وستُمَرَر أية بيانات إضافية لاحقًا للنموذج له عبر المُرَمِّز.
ملاحظة: إذا كنت تخطط لاستخدام أحد النماذج المُدَّربة مسبقًا بدلًا من تدريب نموذجك من الصفر، فاحرص على معالجة بياناتك بالمُرَمِّز نفسه الذي تَدّرَبَ عليه النموذج الجاهز قبل تمريرها له، فهذا يعني أن يُقَسَّم النص الخاص بك بالطريقة نفسها التي قُسِّمَت بها بيانات تدريب النموذج، وأن يُرَمَّز برموزه نفسها (التي يشار إليها بالمفردات vocab).
لنبدأ بالتطبيق العملي، حَمِّلْ في البداية مُرَمِّزًا tokenizer يناسب النموذج الذي اخترته، ويتضمن المفردات vocab التي تَدَّرَبَ عليها سابقًا، وذلك بواسطة التابع AutoTokenizer.from_pretrained()
وفق التالي:
>>> from transformers import AutoTokenizer >>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")
ثم مَرر النص للمُرَمِّز وستحصل على الخرج التالي:
>>> encoded_input = tokenizer("Do not meddle in the affairs of wizards, for they are subtle and quick to anger.") >>> print(encoded_input) {'input_ids': [101, 2079, 2025, 19960, 10362, 1999, 1996, 3821, 1997, 16657, 1010, 2005, 2027, 2024, 11259, 1998, 4248, 2000, 4963, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
يعطي المُرَمِّز في خرجه قاموسًا dictionary يتضمن ثلاثة أنواع من العناصر هي:
-
input_ids
: الدلالات العددية المقابلة لكل رمز من رموز الجملة أي لكل token. -
attention_mask
: يشير إلى الرموز المهمة التي ينبغي الانتباه لها. -
token_type_ids
: تبين السلسلة التي ينتمي إليها كل رمز، وهي مفيدة عندما تمرر أكثر من سلسلة للمُرَمِّز.
يمكنك فك ترميز الخرج السابق لاستعادة النص الأصلي كما يلي:
>>> tokenizer.decode(encoded_input["input_ids"]) '[CLS] Do not meddle in the affairs of wizards, for they are subtle and quick to anger. [SEP]'
لاحظ أنك استعدت النص مع رمزين إضافيين هما CLS
و SEP
واحد في مقدمة الجملة، وواحد في نهايتها أضافهما المُرَمِّز، قد لا تتطلب جميع النماذج وجود هذين الرمزين لكنَّ المُرَمِّز يُضيفهما تلقائيًا، علمًا أن الرمز CLS
هو اختصار لكلمة المُصَنِّف classifier والرمز SEP
اختصار لكلمة الفاصل separator.
أما إذا رغبت بمعالجة عدة جمل في آنٍ واحد، فمَرِرها للمُرَمِّز بهيئة قائمة list كما في المثال التالي:
>>> batch_sentences = [ "But what about second breakfast?", "Don't think he knows about second breakfast, Pip.", "What about elevensies?", ] >>> encoded_inputs = tokenizer(batch_sentences) >>> print(encoded_inputs) {'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102], [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102], [101, 1327, 1164, 5450, 23434, 136, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1]]}
الحشو Pad
يشترط النموذج أن تكون جميع tensors المدخلة إليه بنفس الطول أي بنفس عدد المحارف، وهذا الأمر لا يمكن تحقيقه من دون معالجة النصوص، فالجمل النصية التي نتعامل معها مختلفة الأطوال في معظم الحالات إن لم يكن في جميعها، لذا نلجأ للحشو pad في تطبيقات معالجة اللغة الطبيعية أي إضافة رموز خاصة للجمل القصيرة تسمى رموز الحشو padding token لزيادة طولها حتى تتساوى مع الجمل الطويلة فنحصل على طول موحد للمدخلات.
اضبط المعامل padding
على القيمة True
لتُفَعِّل ميزة الحشو في برنامجك كما في المثال التالي:
>>> batch_sentences = [ "But what about second breakfast?", "Don't think he knows about second breakfast, Pip.", "What about elevensies?", ] >>> encoded_input = tokenizer(batch_sentences, padding=True) >>> print(encoded_input) {'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0], [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102], [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
كما تلاحظ فإن الجملة الثانية في المثال السابق هي الأطول، لذا أضفنا عددًا من الأصفار لكل من الجملة الأولى والثالثة حتى تتساوى معها بالطول.
الاقتطاع Truncation
الاقتطاع Truncation هي الحالة المعاكسة للحشو، فقد تصادف في بعض الأحيان سلاسل طويلة جدًا أكبر من الحد المسموح به في النماذج فلا يستطيع النموذج التعامل معها، نلجأ في الحالة لاقتطاع جزء من السلسلة حتى تصبح أقصر.
يمكنك استخدام هذه الخاصية بضبط قيمة المعامل truncation
على True
ليقطتع المُرَمِّز من طول السلسلة المدخلة حتى تتناسب مع الحد الأقصى للطول الذي يقبله النموذج:
>>> batch_sentences = [ "But what about second breakfast?", "Don't think he knows about second breakfast, Pip.", "What about elevensies?", ] >>> encoded_input = tokenizer(batch_sentences, padding=True, truncation=True) >>> print(encoded_input) {'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0], [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102], [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
يمكنك مطالعة دليل الحشو والاقتطاع على منصة Hugging Face لمعرفة المزيد حول وسطاء arguments هاتين العمليتين.
بناء الموترات Tensors
إن عناصر الموترات أو التنسورات tensors هي المخرجات النهائية التي نريدها من المُرَمِّز ففي نهاية الأمر هي المدخلات الوحيدة التي يقبلها النموذج، يمكنك الحصول عليها بضبط قيمة المعامل return_tensors
حسب إطار عمل الذكاء الاصطناعي الذي تعتمده.
فإذا كنت تستخدم إطار العمل بايتورش Pytorch، اضبط قيمة المعامل return_tensors
على pt
وفق التالي:
>>> batch_sentences = [ "But what about second breakfast?", "Don't think he knows about second breakfast, Pip.", "What about elevensies?", ] >>> encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt") >>> print(encoded_input) {'input_ids': tensor([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0], [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102], [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]])}
وإذا كنت تستخدم إطار العمل TensorFlowK، اضبط قيمة المعامل return_tensors
على tf
كما في المثال التالي:
>>> batch_sentences = [ "But what about second breakfast?", "Don't think he knows about second breakfast, Pip.", "What about elevensies?", ] >>> encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="tf") >>> print(encoded_input) {'input_ids': <tf.Tensor: shape=(2, 9), dtype=int32, numpy= array([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0], [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102], [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)>, 'token_type_ids': <tf.Tensor: shape=(2, 9), dtype=int32, numpy= array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)>, 'attention_mask': <tf.Tensor: shape=(2, 9), dtype=int32, numpy= array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)>}
ملاحظة: تختلف خطوط الأنابيب في قبولها لوسطاء المُرَمِّز عند استدعائها ()__call__
، فعلى سبيل المثال يدعم خط الأنابيب text-2-text-generation
الوسيط truncation
فقط، بينما يسمح خط الأنابيب text-generation
بتمرير ثلاثة وسطاء هي text-generation
و truncation
و add_special_tokens
، أما في خط الأنابيب fill-mask
فتُمرر وسطاء المُرَمِّز بهيئة قاموس ضمن الوسيط tokenizer_kwargs
.
معالجة البيانات الصوتية
إن أداة المعالجة في المهام الصوتية هي مُستَخرِج الميزات feature extractor حيث تستخدمه لمعالجة البيانات الصوتية الخام وإعدادها قبل إدخالها للنموذج، فيعمل على استخراج الميزات منها ثم تحوّيلها إلى tensors.
سنبدأ أولًا بتحميل مجموعة بيانات صوتية مثل MInDS-14 dataset كما يلي:
>>> from datasets import load_dataset, Audio >>> dataset = load_dataset("PolyAI/minds14", name="en-US", split="train")
اطلب العنصر الأول من العمود audio
من مجموعة البيانات لتأخذ لمحة عن تنسيق الدخل الذي نتعامل معه، علمًا أنه بمجرد استدعاء عمود audio
سيتحمل الملف الصوتي تلقائيًا ويُعاد أخذ العينات منه:
>>> dataset[0]["audio"] {'array': array([ 0. , 0.00024414, -0.00024414, ..., -0.00024414, 0. , 0. ], dtype=float32), 'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav', 'sampling_rate': 8000}
يتضمن الخرج السابق ثلاثة عناصر:
-
array
: المصفوفة هي الإشارة الصوتية المُحَمَّلة للكلام، والتي سيُعاد أخذ العينات منها وتشكيلها بهيئة مصفوفة أحادية البعد -
path
: يُشير إلى مسار تخزين الملف الصوتي -
sampling_rate
: معدل أخذ العينات، وهو عدد العينات المأخوذة من الإشارة الصوتية في الثانية
استُخدِمَ النموذج Wav2Vec2 في هذا المقال، وإذا قرأت توصيفه ستجد أن معدل أخذ العينات في البيانات الصوتية التي تَدَّرَب عليها هو 16KHz، وبالتالي لضمان سلامة التطبيق بنبغي أن نستخدم المعدل نفسه في البيانات الصوتية التي سنمررها للنموذج، لذا تفقد دائمًا معدل أخذ العينات في بياناتك الصوتية فإذا كان مختلفًا عن معدل النموذج فينبغي لك إعادة أخذ العينات منها وتسمى هذه العملية resample ليصبح لها نفس معدل النموذج، وهذه هي الخطوة الأولى التي سنطبقها تاليًا:
1. استخدم التابع cast_column
الخاص بمجموعات البيانات Datasets لرفع معدل أخذ العينات في مجموعتنا إلى 16KHz:
>>> dataset = dataset.cast_column("audio", Audio(sampling_rate=16_000))
2. استدعِ العمود audio
من مجموعة البيانات ليُعاد أخذ العينات منه وفق المعدل الجديد وتشكيل الملف الصوتي:
>>> dataset[0]["audio"] {'array': array([ 2.3443763e-05, 2.1729663e-04, 2.2145823e-04, ..., 3.8356509e-05, -7.3497440e-06, -2.1754686e-05], dtype=float32), 'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav', 'sampling_rate': 16000}
والآن سنُحمّل مستخرج الميزات لتسوية الملف الصوتي normalize، ومعالجته بالحشو أو الاقتطاع وتحضيره قبل إدخاله للنموذج، ففي معالجة النصوص كنا نضيف أصفارًا "0" إلى السلاسل النصية القصيرة لزيادة طولها، وهنا أيضًا سنضيف أصفارًا "0" إلى المصفوفة الصوتية array
فهي لا تؤثر على معناها لأنها تفُسَّر بصفتها لحظات صامتة.
حمّل إذًا مُستَخرِج الميزات المناسب لنموذجك بواسطة AutoFeatureExtractor.from_pretrained()
كما يلي:
>>> from transformers import AutoFeatureExtractor >>> feature_extractor = AutoFeatureExtractor.from_pretrained("facebook/wav2vec2-base")
ثم مَرر المصفوفة الصوتية array
إلى مستخرج المميزات مع ضبط قيمة الوسيط sampling_rate
على معدل أخذ العينات المرغوب لضمان تصحيح أي أخطاء قد تحدث:
>>> audio_input = [dataset[0]["audio"]["array"]] >>> feature_extractor(audio_input, sampling_rate=16000) {'input_values': [array([ 3.8106556e-04, 2.7506407e-03, 2.8015103e-03, ..., 5.6335266e-04, 4.6588284e-06, -1.7142107e-04], dtype=float32)]}
يمكنك تطبيق الحشو والاقتطاع هنا للتعامل مع السلاسل المتغيرة كما طبقناه مع المُرَمِّز tokenizer، ألقِ نظرة على طول العينتين الصوتيتين أدناه:
>>> dataset[0]["audio"]["array"].shape (173398,) >>> dataset[1]["audio"]["array"].shape (106496,)
أنشئ الدالة preprocess_function
التالية لمعالجة مجموعة البيانات حتى تصبح جميع العينات الصوتية بطولٍ واحد، كل ما عليك هو تحديد الحد الأقصى لطول العينة، وسيعمل مستخرج الميزات على حشو السلاسل أو اقتطاعها لتصل للطول المطلوب:
>>> def preprocess_function(examples): audio_arrays = [x["array"] for x in examples["audio"]] inputs = feature_extractor( audio_arrays, sampling_rate=16000, padding=True, max_length=100000, truncation=True, ) return inputs
ثم طبِّق الدالة preprocess_function
على أول بضع عينات من مجموعة البيانات:
>>> processed_dataset = preprocess_function(dataset[:5])
أصبحت جميع العينات الآن بالطول نفسه الذي يماثل الحد الأقصى لطول العينة، ويمكننا تمريرها للنموذج:
>>> processed_dataset["input_values"][0].shape (100000,) >>> processed_dataset["input_values"][1].shape (100000,)
الرؤية الحاسوبية
يستخدم معالج الصور image processor في مشاريع الرؤية الحاسوبية لتجهيز الصور قبل إدخالها للنموذج؛ وتتكون معالجة الصور من عدة مراحل تتحول الصور في نهايتها إلى مدخلات يقبلها النموذج، ومن المراحل على سبيل المثال: تغيير الحجم resizing، والتسوية normalizing، وتصحيح القناة اللونية color channel correction، وأخيرًا تحويل الصور إلى tensors.
إن تحسين الصور أو تعزير الصور image augmentation هو الأسلوب الأكثر استخدامًا في المعالجة المسبقة للصور، وهذه أبرز الفوائد المرجوة من كليهما:
- تعمل ميزة تعزيز الصور image augmentation على تعديل الصور بطريقة تساعدك على الحد من الفرط في التخصيص أي المبالغة في ملائمة البيانات المدخلة للبيانات التي تدرب عليها النموذج، يزيد ذلك من واقعية النموذج ويُحَسِّن أدائه، يمكنك أن تبدع في تعزيز بياناتك وإنشاء نسخ عنها، اضبط السطوع والألوان مثلًا، أو اقتطع من الصور، أو عدّل تدويرها، أو غيّر حجمها بالتكبير أو التصغير أو غير ذلك، ولكن حافظ دائمًا على معناها.
- تضمن المعالجة المسبقة للصور image preprocessing مطابقة صورك لتنسيق الدخل الذي يقبله النموذج، فالصور المُمَرَّرة لنموذج الرؤية الحاسوبية ينبغي أن تُعالج بالطريقة نفسها التي عولجت فيها الصور التي تَدَّرَبَ عليها النموذج عند تدريبه وضبطه وتسمى عملية الصقل fine-tune.
ملاحظة: احرص على استخدام معالج الصور ImageProcessor
المرتبط بالنموذج الذي اخترته، ولجهة تعزيز الصور فيمكنك استخدام أي مكتبة تريدها لإنجاز ذلك.
لنتعلم معًا كيف نستخدم معالج الصور على مجموعة بيانات كاملة، حمّل في البداية مجموعة البيانات food101 dataset وفق الأمر التالي، ويمكنك الاطلاع على دليل تحميل مجموعات البيانات من Hugging Face لمزيد من المعلومات عن طريقة تحميلها:
ملاحظة: سنستخدم هنا المعامل split
لأخذ عينة بسيطة من مجموعة البيانات لأنها كبيرة الحجم.
>>> from datasets import load_dataset >>> dataset = load_dataset("food101", split="train[:100]")
والآن لنأخذ الصورة الأولى من مجموعة البيانات باستخدام الخاصية image
كما يلي:
>>> dataset[0]["image"]
حمّل بعدها معالج الصور المناسب لنموذجك باستخدام التابع AutoImageProcessor.from_pretrained()
كما يلي:
>>> from transformers import AutoImageProcessor >>> image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
يمكننا الآن إضافة بعض الصور باستخدام تعزيز الصور، تستطيع اختيار المكتبة التي تريدها لتطبيق ذلك، مثل Albumentations أو Kornia notebooks، أما في هذا المقال فقد استُخدِمَتْ الوحدة torchvision من مكتبة المحولات transforms وفق الخطوات التالية:
-
سنستخدم اثنين من التحويلات transforms لتعديل الصور في مثالنا، التحويل الأول RandomResizedCrop لتغيير حجم الصور، والثاني ColorJitter لتغيير خصائصها مثل السطوع والتشبع، وسنستخدم الصنف Compose لدمج التحويلين معًا، علمًا أننا نستطيع استخلاص متطلبات الحجم الذي يقبله النموذج من خصائص
image_processor
إذ تشترط بعض النماذج قياساتٍ محددة للارتفاع والعرض، ويقتصر بعضها الآخر على تحديد أقصر حافةshortest_edge
فقط.
>>> from torchvision.transforms import RandomResizedCrop, ColorJitter, Compose >>> size = ( image_processor.size["shortest_edge"] if "shortest_edge" in image_processor.size else (image_processor.size["height"], image_processor.size["width"]) ) >>> _transforms = Compose([RandomResizedCrop(size), ColorJitter(brightness=0.5, hue=0.5)])
-
سننشئ دالةً تجمع بين وظيفتي معالجة الصور وتعزير الصور (التي أجريناها في الخطوة السابقة)، تُطَبَّق هذه الدالة على دفعات الصور التي سنمررها لها وتعطينا في النهاية
pixel_values
التي تُعدّ مدخلات النموذج، إذ إن المرحلة الثانية من الدالة تتضمن استخدام معالج الصور، وهو يتكفل بتسوية الصور وتحويلها إلى tensors مناسبة للنموذج، ألقِ نظرة على الأوامر التالية لتتضح لك الفكرة:
>>> def transforms(examples): images = [_transforms(img.convert("RGB")) for img in examples["image"]] examples["pixel_values"] = image_processor(images, do_resize=False, return_tensors="pt")["pixel_values"] return examples
لابُدّ أنك لاحظت أننا ضبطنا المعامل الخاص بتعديل حجم الصور في معالج الصور على القيمة do_resize=False
، لأننا لا نحتاجه هنا فقد أنجزنا تعديل حجم الصور بالفعل في مرحلة تعزيز الصور (أي في الخطوة الأولى) مستفيدين من خاصية الحجم في معالج الصور image_processor
لمعرفة حدود الحجم المقبولة في النموذج، أما إذا لم تعدل حجم الصور في مرحلة تعزيز الصور، فاترك المعامل do_resize
على قيمته الافتراضية وسيُعَدِّل معالج الصور أحجام صورك بما يتناسب مع متطلبات النموذج.
وبالمقابل إذا رغبت بإجراء تسوية normalize للصور ضمن مرحلة تعزيز الصور فاستخدم عندها الخاصيتين image_processor.image_mean
و image_processor.image_std
.
-
استخدم الآن
datasets.set_transform
لتسريع نشر التحويلات السابقةtransforms
على مجموعة البيانات:
>>> dataset.set_transform(transforms)
-
استدعِ الصورة الأولى من مجموعة البيانات ولاحظ كيف تغيرت فقد أضاف إليها التحويل قيم
pixel_values
، وبذلك أصبحت مجموعة البيانات dataset جاهزة لإدخالها إلى النموذج:
dataset[0].keys()
ألقِ نظرة على الصورة بعد التعديل، فقد اقتطع جزءٌ عشوائيٌ منها، وتغيرت خصائصها اللونية.
>>> import numpy as np >>> import matplotlib.pyplot as plt >>> img = dataset[0]["pixel_values"] >>> plt.imshow(img.permute(1, 2, 0))
ملاحظة:لا يقتصر عمل معالج الصور ImageProcessor على المعالجة المُسبقة أو التحضيرية للبيانات قبل إدخالها للنموذج، بل يستخدم أيضًا في المعالجة اللاحقة post processing فيعالج المخرجات الأولية لنموذج الذكاء الاصطناعي ويحولها إلى تنبؤات لها معنى مثل المربعات المحيطة bounding boxes وخرائط التقسيم segmentation maps.
هذا مفيد في مهام مثل التَعرُّف على الكائنات object detection، والتجزئة الدلالية للصور semantic segmentation أي تقسيم الصورة إلى أجزاء بحيث يعبر كل جزء عن صنف معينة، وتجزئة المثيل instance segmentation وهي تشابه التجزئة الدلالية ولكن بالإضافة إلى تحديد الأصناف، تفصل المثيلات الفردية داخل نفس الصنف، وتجزئة panoptic segmentation الشاملة التي تنتج خرائط تقسيم شاملة تحدد كل جزء من الصورة وتوضح الصنف أو الكائن الذي ينتمي إليه.
الحشو Pad
تطبقُ بعض النماذج عملية تعزيز الصور في أثناء التدريب، مثل نموذج DETR المخصص للتعرف على الكائنات، وتسبب هذه الحالة اختلافًا في أحجام الصور ضمن الدفعة الواحدة batch، يتعامل DETR مع هذه الحالة باستخدام تابع الحشو image_processor.pad()
من الصنف DetrImageProcessor
مع وضع قناع "pixel_mask"
يظهر أي البيكسلات هي بيكسلات الحشو، بالإضافة إلى تعريف الدالة collate_fn
وفق التالي لتجميع الصور:
>>> def collate_fn(batch): pixel_values = [item["pixel_values"] for item in batch] encoding = image_processor.pad(pixel_values, return_tensors="pt") labels = [item["labels"] for item in batch] batch = {} batch["pixel_values"] = encoding["pixel_values"] batch["pixel_mask"] = encoding["pixel_mask"] batch["labels"] = labels return batch
الأنماط المتعددة
يُستَخدم المعالج processor لتحضير البيانات متعددة الأنماط multimodal قبل إدخالها لنماذج الذكاء الاصطناعي، ويجمع المعالج الواحد أكثر من وظيفة معالجة من الوظائف التي تعرفنا عليها، مثل المُرَمِّز ومُستَخرِج الميزات أو غير ذلك حسب مزيج الأنماط الموجود في بياناتك نصوص أو صور أو صوت.
حمِّل مثلًا مجموعة البيانات LJ Speech متعددة الأنماط وطبق المثال التالي لتتعلم طريقة التَعَرُّف التلقائي على الكلام (ASR) فيها:
>>> from datasets import load_dataset >>> lj_speech = load_dataset("lj_speech", split="train")
سنُرَكِز اهتمامنا على الصوت audio
والنص text
في مهام التَعَرُّف التلقائي على الكلام ASR لذا سنبدأ بإزالة الأعمدة الأخرى من مجموعة البيانات وفق التالي:
>>> lj_speech = lj_speech.map(remove_columns=["file", "id", "normalized_text"])
لنستعرض الآن عمودي الصوت والنص:
>>> lj_speech[0]["audio"] {'array': array([-7.3242188e-04, -7.6293945e-04, -6.4086914e-04, ..., 7.3242188e-04, 2.1362305e-04, 6.1035156e-05], dtype=float32), 'path': '/root/.cache/huggingface/datasets/downloads/extracted/917ece08c95cf0c4115e45294e3cd0dee724a1165b7fc11798369308a465bd26/LJSpeech-1.1/wavs/LJ001-0001.wav', 'sampling_rate': 22050} >>> lj_speech[0]["text"] 'Printing, in the only sense with which we are at present concerned, differs from most if not from all the arts and crafts represented in the Exhibition'
كما تعلمنا في الفقرات السابقة ينبغي أن يتطابق معدل أخذ العينات في بياناتنا الصوتية المدخلة للنموذج مع معدل أخذ العينات في البيانات الصوتية التي تَدَرَّبَ عليها النموذج سابقًا، لذا سنجري عملية إعادة أخذ للعينات resample في بياناتنا وفق التالي:
>>> lj_speech = lj_speech.cast_column("audio", Audio(sampling_rate=16_000))
يمكننا الآن تحميل المعالج المناسب لحالتنا باستخدام AutoProcessor.from_pretrained()
:
>>> from transformers import AutoProcessor >>> processor = AutoProcessor.from_pretrained("facebook/wav2vec2-base-960h")
يتبقى لنا خطوتان قبل تمرير البيانات للنموذج:
-
إنشاء الدالة
prepare_dataset
التالية التي ستُعَالِج البيانات الصوتية الموجودة فيarray
(أحد عناصر الخرج لمستخرج الميزات كما تعلمنا في فقرة الصوتيات) وتُحَوّلها إلىinput_values
، وأيضًا تُرَمِّز tokenize النصtext
ليتحول إلىlabels
، وبذلك نكون قد جهزّنا مُدخلات النموذج:
>>> def prepare_dataset(example): audio = example["audio"] example.update(processor(audio=audio["array"], text=example["text"], sampling_rate=16000)) return example
- 2. تطبيق الدالة على مجموعة البيانات dataset، سنُطّبِّقها هنا على عينة منها:
>>> prepare_dataset(lj_speech[0])
إذًا فقد جَهَّز المعالج بياناتنا للنموذج والتي تتكون من input_values
و labels
، وضبط معدل أخذ العينات على 16KHz، وبالتالي يمكننا الآن تمرير البيانات إلى النموذج.
الخلاصة
المعالجة المسبقة للبيانات قبل تمريرها لنماذج الذكاء الاصطناعي خطوة أساسية لا غنى عنها في أي مشروع، وتختلف أساليب المعالجة حسب نوع بياناتك صوت أو صورة أو نص أو غيرها، لكنها تنتهي دائمًا بتحويل البيانات الخام إلى tensors يقبلها النموذج، ولا تنسَ أن الأصناف التلقائية في مكتبة Transformers مثل: AutoTokenizer
و AutoProcessor
و AutoImageProcessor
و AutoFeatureExtractor
تساعدك على اختيار المعالج المسبق المناسب لنموذجك، وهذا مهمٌ جدًا عند استخدامك النماذج المدربة مسبقًا إذ ينبغي تُعالج بيانات مشروعك بالمعايير نفسها التي عولجت بها البيانات التي تَدَرَّبَ عليها النموذج قبلًا فتستخدم المفردات vocab نفسها مثلًا في مهام معالجة النص، ومعدل أخذ العينات نفسه والمهام الصوتية وما إلى ذلك.
ترجمة -وبتصرف- لقسم Preprocess من توثيقات Hugging Face.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.