الوحدة 1 — الواجهة: المدخلات والمخرجات والدالّة
النموذج الذي تنبض حياته داخل دفتر Jupyter لا يستطيع زميلك تجربته. Gradio يُصلح هذا الوضع في عشرة أسطر: تُعرّف دالّة Python تأخذ مُدخَلات وتُرجع مُخرَجات، تُعرّف مكوّنات الواجهة، تُشغّل الخادم، ثمّ ترسل رابطًا في المحادثة. هذه الوحدة تفكّك كلّ عنصر في هذه الأسطر العشرة.
ما هو Gradio ولماذا هنا
Gradio مكتبة Python تُولّد واجهة ويب فوق دالّة موجودة. لا HTML، ولا JavaScript، ولا خادم Flask، ولا CORS. تُثبَّت بأمر واحد ثمّ تُستدعى مباشرة:
pip install gradio
الهدف ليس أن تصير Gradio هي الإنتاج، بل أن تختصر المسافة بين نموذج جاهز ومستخدم يستطيع تجربته اليوم. في الميدان يُستعمل هذا للعروض الداخلية، ومراجعات العملاء، والتحقيق على البيانات مع خبراء المجال، وجمع الملاحظات المبكّرة قبل أن يُبنى مسار خدمة كامل. من يعرف كم يستغرق بناء واجهة React مصغّرة يُدرك قيمة عشر دقائق مقابل يومين.
الحساب الكامل لواجهة gr.Interface
المفهوم المركزي هو ثلاثة عناصر: دالّة، ومكوّنات المُدخَل، ومكوّنات المُخرَج. مثال أبسط تدفّ ق:
import gradio as gr
def saluer(nom: str) -> str:
return f"مرحبًا يا {nom}!"
demo = gr.Interface(
fn=saluer,
inputs=gr.Textbox(label="اسمك"),
outputs=gr.Textbox(label="الرسالة"),
title="مرحبًا Gradio",
description="أوّل واجهة لك في عشرة أسطر.",
)
if __name__ == "__main__":
demo.launch()
بمجرّد تشغيل السكربت يُفتح الخادم على http://127.0.0.1:7860، وتظهر واجهة بحقلَيْن وزرّ إرسال. لا حاجة إلى إعداد إضافي. Gradio يستدعي saluer بقيمة الحقل، ويعرض ما تُرجعه في الحقل الثاني.
مطابقة أنواع Python بالمكوّنات
Gradio يعرف كيف يُخمّن مكوّن الواجهة من التلميح الوارد في توقيع الدالّة، لكن الأفضل دائمًا أن يكون التصريح صريحًا. الجدول التالي يُلخّص التطابقات الشائعة:
| نوع Python | مكوّن Gradio | ما يستقبله المُبرمِج |
|---|---|---|
str قصير | gr.Textbox() | سلسلة نصّيّة |
int أو float | gr.Number() أو gr.Slider() | عدد |
bool | gr.Checkbox() | قيمة منطقيّة |
| ملفّ صورة | gr.Image() | مصفوفة NumPy أو PIL أو مسار حسب type |
| ملفّ صوت | gr.Audio() | زوج (sample_rate, np.ndarray) أو مسار |
| قائمة اختيارات | gr.Dropdown(choices=[...]) | القيمة المُختارة |
| بيانات جدولية | gr.Dataframe() | كائن pandas.DataFrame |
كتابة inputs="text" تعمل كاختصار، لكنّ استعمال الصنف الكامل يفتح الخصائص المهمّة (تسمية، قيمة افتراضيّة، معاينة، تحقّق). عند نشر الواجهة، هذه الخصائص هي ما يُميّز عرضًا هاويًا من عرض احترافيّ.
عنوان الواجهة ووصفها
title وdescription ليسا زخرفة، بل هما ما يُبرِّر وجود العرض. الوصف يُجيب على ثلاثة أسئلة يطرحها كلّ زائر جديد: ما هذا؟، وكيف أستعمله؟، وما حدوده؟. تجاهل هذه الأسئلة يُنتج عرضًا يبدو مبهرجًا لكنّه غير قابل للاستعمال بلا شرح شفويّ مصاحب.
demo = gr.Interface(
fn=classer,
inputs=gr.Image(type="pil", label="ارفع صورة"),
outputs=gr.Label(num_top_classes=3, label="أعلى ثلاثة أصناف"),
title="مصنّف الصور — نسخة تجريبيّة",
description=(
"يعتمد هذا العرض على نموذج مُدرَّب على ImageNet. "
"يعمل جيّدًا على الأشياء اليوميّة، ويفشل على الفنون التجريديّة "
"والصور ذات الدقّة المنخفضة جدًّا."
),
article="**تنبيه**: الصور لا تُخزَّن على الخادم.",
)
article يُعرض بعد الواجهة بصيغة Markdown، وهو المكان الطبيعيّ لسياسة الخصوصيّة، وحدود المعرفة، وذكر مصادر البيانات.
مصنّف صور جاه ز في عشرة أسطر
نستعمل نموذجًا من الدورة 10 (شبكة التفافية مُدرَّبة مسبقًا) لبناء عرض عمليّ:
import gradio as gr
from torchvision import models, transforms
import torch, json, urllib.request
modele = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2).eval()
etiquettes = json.loads(urllib.request.urlopen(
"https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json"
).read())
transformer = transforms.Compose([
transforms.Resize(256), transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
def classer(image):
x = transformer(image).unsqueeze(0)
with torch.no_grad():
probs = torch.softmax(modele(x)[0], dim=0)
return {etiquettes[i]: float(probs[i]) for i in range(len(etiquettes))}
gr.Interface(
fn=classer, inputs=gr.Image(type="pil"), outputs=gr.Label(num_top_classes=5),
title="مصنّف صور ResNet-50",
).launch()
خلال ثواني الاختبار الأولى ستلاحظ ثلاث ملاحظات مهمّة: النموذج يعمل جيّدًا على القطط والكلاب والأشياء المنزلية، لكنّه يفشل بشكل واثق على مشاهد غير موجودة في ImageNet، ولا يعرف أنّه لا يعرف. هذه الملاحظات ستُقودنا في الوحدات التالية إلى الحاجة لمكوّن gr.Label بعتبة ثقة، وأمثلة تُظهر الحدود، و نظام جمع ملاحظات مستخدمين.
Image(type=...)type="pil" يمرّر كائن PIL، وهو الأسرع للنماذج التي تستقبل PIL مباشرة. type="numpy" يمرّر مصفوفة NumPy بشكل (H, W, 3) بقيم 0..255. type="filepath" يمرّر مسار الملفّ على القرص، وهو مفيد حين تريد تمرير المسار إلى مكتبة تفتحه بنفسها. اختيار خاطئ هنا يعطي شكل بيانات غير متوقّع في الدالّة.
الخلاصة
gr.Interfaceتحتاج ثلاثة عناصر فقط: دالّة، مكوّنات المُدخَل، مكوّنات المُخرَج، وتشغيلها بـlaunch()يفتح خادمًا على7860.- مطابقة أنواع Python بالمكوّنات مسؤولية المُبرمج؛ استعمال الأصناف الكاملة يُفعّل الخصائص المهمّة كالتسمية والقيمة الافتراضيّة.
- العنوان والوصف ليسا زخرفة: هما ما يُجيب عن أسئلة الزائر الأولى ويجعل العرض قابلًا للاستعمال بلا شرح شفويّ.
- مصنّف الصور في عشرة أسطر يُبرهن أنّ الفارق بين نموذج مخبريّ وأداة قابلة للتجربة هو غالبًا واجهة، لا نموذج أفضل.
الوحدة التالية: مكوّنات النصّ والصورة والصوت والفيديو، وما تستقبله كلّ واحدة بالضبط.