اذهب إلى المحتوى

كيفية بناء شبكة عصبية لترجمة لغة الإشارة إلى اللغة الإنجليزية


هدى جبور

الرؤية الحاسوبيّة Computer vision هي مجال فرعي من علوم الحاسوب Computer science، تهدف إلى استخراج معلومات عالية المستوى من الصور ومقاطع الفيديو. يتضمن هذا المجال مهام مثل اكتشاف الكائنات في صورة أو فيديو وتحسين واستعادة الصور (إكمال المصفوفة) وتتبع العناصر في المقاطع المتحركة. ويُغذّي هذا المجال العديد من التقنيات، بما في ذلك مرشحات الدردشة المرئية video chat filters والتعرّف على الوجه (مصادقة الوجه Face Authentication) في الأجهزة المحمولة والسيارات ذاتية القيادة.

نستخدم خلال هذه المقالة الرؤية الحاسوبيّة لإنشاء مُترجم للغة الإشارة الأمريكية واستخدامها مع كاميرا الويب الخاصة بك. نستخدم خلال هذه المقالة مكتبة الرؤية الحاسوبية الشهيرة أوبين سي في OpenCV وإطار العمل بايتورش PyTorch لبناء شبكة عصبية عميقة، والنظام البيئي أونكس ONNX لتصدير الشبكة العصبية التي سوف نبنيها. نُطبّق المفاهيم التالية أثناء إنشاء تطبيق الرؤية الحاسوبيّة خاصتنا:

  • نتبع طريقة من ثلاث خطوات وهي: المعالجة المسبقة لمجموعة البيانات ثم تدريب النموذج وأخيرًا تقيّيم النموذج.
  • تحسين كل خطوة من خلال استخدام تقنية زيادة أو تعزيز البيانات Data augmentation لمعالجة حالات الأيدي المدورة أو غير المتمركزة (قد تكون الأيدي في زوايا أو اتجاهات مختلفة) وتغيير جداول مُعدّل التعلّم Learning rate schedule لتحسين دقة النموذج وتصدير النماذج لزيادة سرعة الاستدلال.
  • نستكشف أيضًا المفاهيم ذات الصلة بتعلّم الآلة Machine Learning.

بعد الانتهاء من هذه المقالة، سيكون لديك مُترجم لغة الإشارة الأمريكية ومعرفة أساسيّة بالتعلّم العميق Deep Learning. يمكنك أيضًا الوصول إلى الشيفرة المصدرية الكاملة لهذا المشروع هنا.

المتطلبات الأساسية

لإكمال هذه المقالة سوف تحتاج إلى ما يلي:

الخطوة 1 -إنشاء المشروع وتثبيت التبعيات

نُنشئ الآن مُجلّد عمل لهذا المشروع ونُثبّت التبعيات التي نحتاجها.

في توزيعات لينكس Linux، ابدأ بإعداد مُدير حزم النظام الخاص بك وثبّت حزمة virtualenv. استخدم:

$ apt-get update
$ apt-get upgrade
$ apt-get install python3-venv

سوف نسمي مُجلّد العمل SignLanguage:

$ mkdir ~/SignLanguage

ننتقل إلى المجلد:

$ cd ~/SignLanguage

أنشئ بيئة افتراضية جديدة للمشروع:

$ python3 -m venv signlanguage

نشّط البيئة:

$ source signlanguage/bin/activate

ثبّت بايتورش PyTorch، وهو إطار عمل للتعلم العميق في بايثون.

ثبّت بايتورش على نظام التشغيل ماك أو إس macOS باستخدام الأمر التالي:

(signlanguage) $ python -m pip install torch==1.2.0 torchvision==0.4.0

استخدم الأوامر التالية من أجل بناء النموذج على وحدة المعالجة المركزية CPU سواءًا في لينكس أو ويندوز:

(signlanguage) $ pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
(signlanguage) $ pip install torchvision

نُثبّت الآن الحزم أوبين سي في opencv ونمباي numpy وأونكس onnx، وهي مكتبات الرؤية الحاسوبيّة والجبر الخطي وتصدير وتنفيذ نماذج الذكاء الاصطناعي على التوالي. تُقدّم opencv أدوات مساعدة مثل تدوير الصور والتقاطها والتعديل عليها، وتُقدّم numpy أدوات مساعدة للجبر الخطي مثل انعكاس وضرب المصفوفات:

(signlanguage) $ python -m pip install opencv-python==3.4.3.18 numpy==1.14.5 onnx==1.6.0 onnxruntime==1.0.0

في توزيعات لينكس، ستحتاج إلى تثبيت libSM.so:

(signlanguage) $ apt-get install libsm6 libxext6 libxrender-dev

بعد تثبيت التبعيات، دعونا نبني الإصدار الأول من مُترجم لغة الإشارة: مُصنّف لغة الإشارة.

الخطوة 2 -إعداد مجموعة بيانات تصنيف لغة الإشارة

في الأقسام الثلاثة التالية، ستبني مُصنّف لغة الإشارة باستخدام شبكة عصبية. هدفك هو إنتاج نموذج يقبل صورة اليد كمدخل ويخرج حرفًا. الخطوات الثلاث التالية مطلوبة لبناء نموذج تعلّم آلي لمهمة التصنيف التي لدينا:

  • المعالجة المسبقة للبيانات: تطبيق تشفير الواحد النشط One-hot encoding على التسميات Labels وتغليف البيانات بموَتِّرات بايتورش (أو تينسرات بايتورش) PyTorch Tensors . تدريب النموذج على البيانات الإضافية من عملية الزيادة augmentation، وذلك لإعداده للإدخال غير العادي، أي عندما تكون اليد في أوضاع غير قياسيّة، مثل اليد غير المتمركزة أو المدورة.
  • تحديد النموذج وتدريبه: إنشاء شبكة عصبية باستخدام بايتورش وتحديد معلمات التدريب العليا hyper-parameters -مثل مدة التدريب- وتشغيل خوارزميّة النزول الاشتقاقي العشوائي stochastic gradient descent. تتضمّن هذه المرحلة أيضًا تعديل إحدى معلمات التدريب العليا أثناء عملية التدريب، وهي قيمة مُعدّل التعلّم learning rate، وذلك من خلال جدول مُعدّل التعلّم، فكما ذكرنا سابقًا؛ هذا سوف يرفع دقة النموذج.
  • التنبؤ باستخدام النموذج: تقييم الشبكة العصبية على بيانات التحقق لفهم دقتها. ثم تصدير النموذج إلى تنسيق يسمى ONNX للحصول على استدلال أسرع.

ملاحظات:

  • تشفير الواحد النشط عبارة عن أشعة Vectors كل شعاع تكون جميع قيمه أصفار ماعدا بعد واحد فقط من الشعاع تكون قيمته واحد. قيمة الواحد تُشير إلى شيء معين. في جالتنا تشير إلى الصنف الذي يُعبر عن العينة المرتبطة به.
  • تُشير موَتِّرات بايتورش PyTorch Tensors إلى هيكل البيانات الأساسي في بايتورش، تُشبه مصفوفات نمباي NumPy مع وظائف إضافية مُحسّنة من أجل دعم عمليات التعلّم العميق Deep Learning. تُعتبر مكونًا أساسيًا يُتيح التنفيّذ الفعّال لحسابات الشبكة العصبية.

في هذا القسم من المقالة ستنجز الخطوة الأولى التي تتضمّن تجهيز مجموعة البيانات ومعالجتها. هذا يتضمّن تنزيل البيانات وإنشاء كائن Dataset لتغليفها والتكرار عليها وتطبيق تقنية زيادة البيانات أخيرًا. في نهاية هذه الخطوة، سيكون لديك طريقة برمجية للوصول إلى الصور والتسميات الموجودة في مجموعة البيانات الخاصة بك لتغذية النموذج.

أولاً، عليك تنزيل مجموعة البيانات إلى مجلد العمل الحالي الخاص بك:

ملاحظة: في نظام التشغيل ماك، لا يتوفر wget افتراضيًا. لذلك يجب عليك تثبيت Homebrew ثم تشغيل برنامج Brew install wget.

(signlanguage) $ wget https://assets.digitalocean.com/articles/signlanguage_data/sign-language-mnist.tar.gz

فك ضغط الملف المضغوط الذي يحتوي على مجلد /data:

(signlanguage) $ tar -xzf sign-language-mnist.tar.gz

أنشئ ملفًا جديدًا باسم step_2_dataset.py:

(signlanguage) $ nano step_2_dataset.py

يجب عليك الآن استيراد الأدوات المساعدة الضرورية، ثم إنشاء الصنف الذي سيُغلّف مجموعات بيانات التدريب والاختبار ويعالجها بالشكل المناسب، وذلك من خلال تنفيذ الواجهة Dataset الخاصة ببايتورش، هذا يسمح لك بتحميل واستخدام خط معالجة (أو خط أنابيب) البيانات المدمج في بايتورش مع مجموعة بيانات تصنيف لغة الإشارة خاصتك:

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import numpy as np
import torch

import csv


class SignLanguageMNIST(Dataset):
    """مجموعة بيانات تصنيف لغة الإشارة

    تُستخدم هذه الفئة كأداة لتحميل مجموعة بيانات لغة الإشارة في بايتورش
    """
    pass

يُمثّل الصنف SignLanguageMNIST مجموعة بيانات تصنيف لغة الإشارة. إنها أداة مساعدة لتحميل مجموعة بيانات لغة الإشارة إلى بايتورش. مجموعة البيانات المستخدمة منشورة على موقع كاغل Kaggle في عام 2017، لمؤلف لم يُذكر اسمه الصريح، لكن اسم المستخدم هو tecperson. كل عينة عبارة عن مصفوفة من الشكل 28281*1، وكل تسمية Label هي قيمة عددية scalar.

احذف الآن العنصر النائب pass من الصنف SignLanguageMNIST. وضع مكانه تابعًا لتوليد قائمة بالتسميات المتوفرة في مجموعة البيانات اسمها mapping، وذلك لربط كل تسمية بفهرس:

@staticmethod
    def get_label_mapping():
        """
    تُعيد قائمة تربط بين تسميات مجموعة البيانات وفهارس الحروف 
    تحويل نطاق فهارس التسميات الأصلية إلى نطاق جديد يبدأ من 0 حتى 23
        """
        mapping = list(range(25))
        mapping.pop(9)
        return mapping

تحتوي التسميات الأصلية في مجموعة البيانات على قيم تتراوح من 0 إلى 25. وتتوافق كل قيمة مع حرف معين، باستثناء الحرفين J و Z المستبعدين. لذلك يوجد 24 تسمية صالحة في النطاق الأصلي (من 0 إلى 25 باستثناء 9 و25). الهدف من هذه الشيفرة هو تعديل مجال قيم التسميات إلى مجال آخر، أي تحويل نطاق فهارس التسميات الأصلية إلى نطاق جديد يبدأ من 0 ويكون متجاورًا حتى 23. وبعبارة أخرى، نطاق الفهارس الجديد هو [0، 1، 2، .. ، 23]، وذلك للتأكد من أن قيم التسمية تكون في تسلسل مضغوط متجاور. بشكل أوضح سيكون الخرج هو القائمة التالية:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]

نلاحظ أن قيم هذه القائمة تبدأ بالعدد 0 الذي يُشير للحرف A ويقابل الفهرس 0، وتنتهي مع العدد 24 والذي يشير للحرف Y ويقابل الفهرس 23. هذا يُسهل المعالجة والتفسير في خطوات المعالجة اللاحقة.

أضف بعد ذلك تابعًا لاستخراج التسميات والعينات من ملف CSV. تفترض الشيفرة التالية أن كل سطر من ملف CSV يبدأ بالتسمية (أي العمود الأول) ثم يتبعه قيم 784 بكسل (باقي الأعمدة). تُمثّل قيم 784 بكسل هذه صورة مقاسها 28 × 28، وكل بكسل تتراوح قيمته بين 0-255:

@staticmethod
    def read_label_samples_from_csv(path: str):
        """
     يُمثّل التسمية وباقي القيم 2^28 هي قيم البكسلات CSV نفترض أن العمود الأول من ملف 
    قيم البكسلات تتراوح بين 0 إلى 255
        """
        mapping = SignLanguageMNIST.get_label_mapping()
        labels, samples = [], []
        with open(path) as f:
            _ = next(f)  # skip header
            for line in csv.reader(f):
                label = int(line[0]) 
                labels.append(mapping.index(label))
                samples.append(list(map(int, line[1:]))) 
        return labels, samples

لاحظ أن كل سطر من الكائن csv.reader القابل للتكرار عبارة عن قائمة من السلاسل. لهذا السبب نحن نستدعي الدالة int و map لتحويل جميع السلاسل إلى أعداد صحيحة.

نضع بعد هذا التابع الثابت static مباشرةً، تابعًا آخر من أجل تهيئة حامل البيانات لدينا:

def __init__(self,
            path: str="data/sign_mnist_train.csv",
            mean: List[float]=[0.485],
            std: List[float]=[0.229]):
        """
        المعطيات:
            path: الذي يحتوي مجموعة البيانات .csv يُستخدم من أجل تحديد مسار ملف
        """
        labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path)
        self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1))
        self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1))

        self._mean = mean
        self._std = std

يبدأ هذا التابع بتحميل العينات والتسميات. ثم يُغلّف البيانات بمصفوفات نمباي. نشرح معلومات المتوسط والانحراف المعياري قريبًا، في قسم __getitem__ التالي.

ملاحظة: ملف csv. الذي يحتوي مجموعة البيانات أعمدته هي: label, pixel0, pixel1, … , pixeln

نضيف التابع __len__ بعد التابع __init__ مباشرةً. تتطلب مجموعة البيانات هذه طريقة لتحديد متى يجب إيقاف التكرار على البيانات، وهذه الطريقة هي التابع __len__ الذي يخبرها بآخر عنصر في مجموعة البيانات:

...
    def __len__(self):
        return len(self._labels)

أخيرًا، أضف التابع __getitem__ الذي يُرجع قاموسًا Dict يحتوي على العينة والتسمية:

def __getitem__(self, idx):
        transform = transforms.Compose([
            transforms.ToPILImage(),
            transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),
            transforms.ToTensor(),
            transforms.Normalize(mean=self._mean, std=self._std)])

        return {
            'image': transform(self._samples[idx]).float(),
            'label': torch.from_numpy(self._labels[idx]).float()
        }

نحن نستخدم تقنية تُعرف باسم زيادة البيانات أثناء التدريب، حيث تخضع العينات لتغييرات لتعزيز مرونة النموذج في فهم هذه الاختلافات (مثلًا حتى لو قمنا بقلب الصورة رأسًا على عقب أو كبّرنا الصورة أو غيرنا بعض الألوان، يجب أن يفهم النموذج أن جميعها تنتمي لنفس التسمية). على وجه التحديد، نُجري عمليات تكبير الصورة عشوائيًا على مستويات ومواضع مختلفة باستخدام RandomResizeCrop. من المهم تسليط الضوء على أن عملية التكبير هذه يجب أن لا تؤثر على تسمية لغة الإشارة النهائية، أي يجب أن تظل التسمية دون تغيير. بالإضافة إلى ذلك، يمكنك تسوية أو تقييس Normalize المدخلات، مما يضمن أن قيم بكسلات الصورة تقع ضمن النطاق [0، 1] بدلاً من [0، 255]. لتحقيق ذلك، استخدم المتوسط والانحراف المعياري لمجموعة البيانات (mean_ و std_) أثناء عملية التقييس.

سيكون الصنف SignLanguageMNIST الكامل كما يلي:

from torch.utils.data import Dataset
from torch.autograd import Variable
import torchvision.transforms as transforms
import torch.nn as nn
import numpy as np
import torch

from typing import List

import csv


class SignLanguageMNIST(Dataset):
    """مجموعة بيانات تصنيف لغة الإشارة

    تُستخدم هذه الفئة كأداة لتحميل مجموعة بيانات لغة الإشارة في بايتورش
    """

    @staticmethod
    def get_label_mapping():
        """
    تُعيد قائمة تربط بين تسميات مجموعة البيانات وفهارس الحروف
            تحويل نطاق فهارس التسميات الأصلية إلى نطاق جديد يبدأ من 0 حتى 23
        """
        mapping = list(range(25))
        mapping.pop(9)
        return mapping

    @staticmethod
    def read_label_samples_from_csv(path: str):
        """
     يُمثّل التسمية وباقي القيم 2^28 هي قيم البكسلات CSV نفترض أن العمود الأول من ملف 
    قيم البكسلات تتراوح بين 0 إلى 255
        """
        mapping = SignLanguageMNIST.get_label_mapping()
        labels, samples = [], []
        with open(path) as f:
            _ = next(f)  # skip header
            for line in csv.reader(f):
                label = int(line[0])
                labels.append(mapping.index(label))
                samples.append(list(map(int, line[1:])))
        return labels, samples

    def __init__(self,
            path: str="data/sign_mnist_train.csv",
            mean: List[float]=[0.485],
            std: List[float]=[0.229]):
        """
        المعطيات:
            path: الذي يحتوي مجموعة البيانات .csv يُستخدم من أجل تحديد مسار ملف
        """
        labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path)
        self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1))
        self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1))

        self._mean = mean
        self._std = std

    def __len__(self):
        return len(self._labels)

    def __getitem__(self, idx):
        transform = transforms.Compose([
            transforms.ToPILImage(),
            transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),
            transforms.ToTensor(),
            transforms.Normalize(mean=self._mean, std=self._std)])

        return {
            'image': transform(self._samples[idx]).float(),
            'label': torch.from_numpy(self._labels[idx]).float()
        }

ستتحقق الآن من توابع الأداة المساعدة لمجموعة البيانات الخاصة بنا عن طريق تحميل مجموعة بيانات SignLanguageMNIST. أضف الشيفرة التالية إلى نهاية ملفك بعد الصنف SignLanguageMNIST:

def get_train_test_loaders(batch_size=32):
    trainset = SignLanguageMNIST('data/sign_mnist_train.csv')
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)

    testset = SignLanguageMNIST('data/sign_mnist_test.csv')
    testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False)
    return trainloader, testloader

من خلال هذه الشيفرة تجري عملية تهيئة مجموعة البيانات باستخدام الصنف SignLanguageMNIST، ثم بالنسبة لمجموعات التدريب والتحقق، تُغلّف مجموعة البيانات في DataLoader. يؤدي هذا إلى تحويل مجموعة البيانات إلى نسخة قابلة للتكرار لاستخدامها لاحقًا أثناء تدريب النموذح.

تتحقق الآن من أن الأدوات المساعدة لمجموعة البيانات تعمل، وذلك من خلال استدعاء الدالة get_train_test_loaders التي تُرجع مُحملي بيانات من الصنف DataLoader. نكتفي بواحد منهم للاختبار، ونسنده للمتغير loader، ثم نطبع السطر الأول من هذا المُحمّل. أضف ما يلي إلى نهاية ملفك:

if __name__ == '__main__':
    loader, _ = get_train_test_loaders(2)
    print(next(iter(loader)))

يمكنك التحقق من أن ملفك يطابق ملف step_2_dataset الموجود في هذا المستودع. اخرج من المحرر الخاص بك وشغّل البرنامج بما يلي:

(signlanguage) $ python step_2_dataset.py

يؤدي هذا إلى إخراج الزوج التالي من الموترات tensors. يقوم خط معالجة البيانات الخاص بنا بإخراج عينتين وتسميتين. يشير هذا إلى أن خط معالجة البيانات الخاص بنا جاهز للانطلاق:

{'image': tensor([[[[ 0.4337,  0.5022,  0.5707,  ...,  0.9988,  0.9646,  0.9646],
          [ 0.4851,  0.5536,  0.6049,  ...,  1.0502,  1.0159,  0.9988],
          [ 0.5364,  0.6049,  0.6392,  ...,  1.0844,  1.0844,  1.0673],
          ...,
          [-0.5253, -0.4739, -0.4054,  ...,  0.9474,  1.2557,  1.2385],
          [-0.3369, -0.3369, -0.3369,  ...,  0.0569,  1.3584,  1.3242],
          [-0.3712, -0.3369, -0.3198,  ...,  0.5364,  0.5364,  1.4783]]],


        [[[ 0.2111,  0.2796,  0.3481,  ...,  0.2453, -0.1314, -0.2342],
          [ 0.2624,  0.3309,  0.3652,  ..., -0.3883, -0.0629, -0.4568],
          [ 0.3309,  0.3823,  0.4337,  ..., -0.4054, -0.0458, -1.0048],
          ...,
          [ 1.3242,  1.3584,  1.3927,  ..., -0.4054, -0.4568,  0.0227],
          [ 1.3242,  1.3927,  1.4612,  ..., -0.1657, -0.6281, -0.0287],
          [ 1.3242,  1.3927,  1.4440,  ..., -0.4397, -0.6452, -0.2856]]]]), 'label': tensor([[24.],
        [11.]])}

لقد تحققت الآن من أن خط معالجة البيانات الخاص بك يعمل. بهذا تنتهي الخطوة الأولى -المعالجة المسبقة لبياناتك- والتي تتضمّن الآن تقنية زيادة البيانات لزيادة قوة النموذج. بعد ذلك سوف نبني الشبكة العصبية والمُحسّن.

الخطوة 3: بناء وتدريب مصنف لغة الإشارة باستخدام التعلم العميق

باستخدام خط معالجة بيانات فعّال، ستتمكن الآن من بناء نموذج وتدريبه على البيانات. على وجه التحديد ستبني شبكة عصبية من ست طبقات وتعرّف دالة الخسارة والمحسّن، وأخيرًا أمثلة تحسين optimize دالة الخسارة لتنبؤات الشبكة العصبية الخاصة بك. في نهاية هذه الخطوة، سيكون لديك مُصنّف لغة إشارة فعّال.

أنشئ ملفًا جديدًا يُسمى step_3_train.py:

(signlanguage) $ nano step_3_train.py

قم باستيراد الأدوات التي تحتاجها:

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch

from step_2_dataset import get_train_test_loaders

نُعرّف الآن شبكة عصبية باستخدام بايتورش تتضمن ثلاث طبقات تلافيفية Convolutional Layers، تليها ثلاث طبقات متصلة بالكامل Fully Connected Layers. أضف الشيفرة التالية إلى نهاية البرنامج الموجود لديك:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 6, 3)
        self.conv3 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 48)
        self.fc3 = nn.Linear(48, 24)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

نُهيّئ الآن الشبكة العصبية ونُعرّف دالة الخسارة ونحدد معلمات التحسين العليا عن طريق إضافة الشيفرة التالية إلى نهاية البرنامج:

def main():
    net = Net().float()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

أخيرًا، تُدرّب النموذج على دورتين:

def main():
    net = Net().float()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

    trainloader, _ = get_train_test_loaders()
    for epoch in range(2):  # loop over the dataset multiple times
        train(net, criterion, optimizer, trainloader, epoch)
    torch.save(net.state_dict(), "checkpoint.pth")

تشير دورة تدريب أو "epoch" إلى دورة واحدة كاملة عبر مجموعة البيانات التدريبية خلال مرحلة التدريب. خلال كل دورة تدريب تُعرض كل عينات البيانات الموجودة في مجموعة البيانات التدريبية مرة واحدة تمامًا على النموذج. في نهاية الدالة main تُحفظ معلمات النموذج في ملف يسمى checkpoint.pth.

أضف التعليمة البرمجية التالية إلى نهاية البرنامج الخاص بك لاستخراج الصورة والتسمية من مُحمل مجموعة البيانات ثم غلّف كل منهما في متغير بايتورش:

def train(net, criterion, optimizer, trainloader, epoch):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs = Variable(data['image'].float())
        labels = Variable(data['label'].long())
        optimizer.zero_grad()

        # انتشار أمامي + انتشار عكسي + تحسيّن
        outputs = net(inputs)
        loss = criterion(outputs, labels[:, 0])
        loss.backward()
        optimizer.step()

        # طباعة الإحصاءات
        running_loss += loss.item()
        if i % 100 == 0:
            print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1)))

تُنفّذ هذا الشيفرة أيضًا عملية الانتشار الأمامي على الشبكة العصبية ثم تحسب قيمة الخسارة ثم تُنفّذ الانتشار العكسي وخطوة المُخسّن.

في نهاية الملف، أضف ما يلي لاستدعاء الدالة الرئيسية main:

if __name__ == '__main__':
    main()

تحقق جيدًا من تطابق ملفك مع ما يلي:

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch

from step_2_dataset import get_train_test_loaders


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 6, 3)
        self.conv3 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 48)
        self.fc3 = nn.Linear(48, 25)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


def main():
    net = Net().float()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)

    trainloader, _ = get_train_test_loaders()
    for epoch in range(2):  # loop over the dataset multiple times
        train(net, criterion, optimizer, trainloader, epoch)
    torch.save(net.state_dict(), "checkpoint.pth")


def train(net, criterion, optimizer, trainloader, epoch):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs = Variable(data['image'].float())
        labels = Variable(data['label'].long())
        optimizer.zero_grad()

        # انتشار أمامي + انتشار عكسي + تحسيّن
        outputs = net(inputs)
        loss = criterion(outputs, labels[:, 0])
        loss.backward()
        optimizer.step()

        # طباعة الإحصاءات
        running_loss += loss.item()
        if i % 100 == 0:
            print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1)))


if __name__ == '__main__':
    main()

احفظ الملف، ثم ابدأ تجربتك الأولية عن طريق تشغيل الأمر التالي ولاحظ الخرج الناتج:

(signlanguage) $ python step_3_train.py

[0,     0] loss: 3.208171
[0,   100] loss: 3.211070
[0,   200] loss: 3.192235
[0,   300] loss: 2.943867
[0,   400] loss: 2.569440
[0,   500] loss: 2.243283
[0,   600] loss: 1.986425
[0,   700] loss: 1.768090
[0,   800] loss: 1.587308
[1,     0] loss: 0.254097
[1,   100] loss: 0.208116
[1,   200] loss: 0.196270
[1,   300] loss: 0.183676
[1,   400] loss: 0.169824
[1,   500] loss: 0.157704
[1,   600] loss: 0.151408
[1,   700] loss: 0.136470
[1,   800] loss: 0.123326

للحصول على قيم خسارة أقل، يمكنك زيادة عدد الدورات إلى 5 أو 10 أو حتى 20 دورة. ومع ذلك، بعد فترة معينة من التدريب، ستتوقف قيمة الخسارة في الشبكة عن الانخفاض حتى لو زدنا عدد الدورات (نكون قد علقنا في إحدى القيم الصغرى المحليّة). لتجنب هذه المشكلة، نحتاج إلى تخفيض قيمة مُعدّل التعلّم مع ازدياد عدد دورات التدريب. يمكننا إجراء ذلك من خلال جدولة قيم مُعدّل التعلّم، بحيث نُقلل من معدل التعلم بمرور الوقت.

نُعدّل الدالة main بإضافة السطرين التاليين اللذين يتضمنان تعريف المجدول scheduler واستدعاء scheduler.step. إضافة إلى ذلك، ارفع
عدد الدورات إلى 12:

def main():
    net = Net().float()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)
    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)

    trainloader, _ = get_train_test_loaders()
    for epoch in range(12):  # loop over the dataset multiple times
        train(net, criterion, optimizer, trainloader, epoch)
        scheduler.step()
    torch.save(net.state_dict(), "checkpoint.pth")

تأكد من تطابق ملفك مع ملف الخطوة 3 الموجود في هذا المستودع. يستمر التدريب لمدة 5 دقائق تقريبًا. سيشبه الخرج الخاص بك ما يلي:

[0,     0] loss: 3.208171
[0,   100] loss: 3.211070
[0,   200] loss: 3.192235
[0,   300] loss: 2.943867
[0,   400] loss: 2.569440
[0,   500] loss: 2.243283
[0,   600] loss: 1.986425
[0,   700] loss: 1.768090
[0,   800] loss: 1.587308
...
[11,     0] loss: 0.000302
[11,   100] loss: 0.007548
[11,   200] loss: 0.009005
[11,   300] loss: 0.008193
[11,   400] loss: 0.007694
[11,   500] loss: 0.008509
[11,   600] loss: 0.008039
[11,   700] loss: 0.007524
[11,   800] loss: 0.007608

الخسارة النهائية التي حصلنا عليها هي 0.007608، وهي أصغر بثلاث مرات من قيمة الخسارة في أول دورة تدريبية 3.20. نختم بهذا الخطوة الثانية من سير العمل لدينا، حيث بنينا الشبكة العصبية ودرّبناها.

رغم أن قيمة الخسارة هذه صغيرة، إلا أنها قد لاتعطينا نظرة ثاقبة عن أداء النموذج. لذا سنحسب دقة Accuracy النموذج، وهي النسبة المئوية للصور التي صنفها النموذج بطريقة صحيحة.

الخطوة 4 -تقييم مصنف لغة الإشارة

نُقيّم الآن مصنف لغة الإشارة عن طريق حساب دقته على مجموعة التحقق، وهي مجموعة من الصور لم يراها النموذج أثناء التدريب، أي أنه لايعرفها. سيوفر هذا إحساسًا أفضل بأداء النموذج مقارنةً بقيمة الخسارة النهائية وحدها. علاوة على ذلك، سنضيف أدوات مساعدة لحفظ نموذجنا المُدرّب في نهاية التدريب وتحميل نموذجنا المدرّب مسبقًا عند إجراء الاستدلال.

أنشئ ملفًا جديد يسمى step_4_evaluate.py:

(signlanguage) $ nano step_4_evaluate.py

استيراد الأدوات التي تحتاجها:

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch
import numpy as np

import onnx
import onnxruntime as ort

from step_2_dataset import get_train_test_loaders
from step_3_train import Net

عرّف بعد ذلك أداةً مساعدةً لتقييم أداء الشبكة العصبية. تُجري الدالة التالية مقارنةً بين الحرف المتوقع من قبل الشبكة العصبية والحرف الحقيقي لصورة واحدة مُمثّلًا بقيمته الأصلية (أي 1،2،3 … ) وليس بترميز الواحد النشط.

def evaluate(outputs: Variable, labels: Variable) -> float:
    """تقييم جودة توقعات النموذج، مقارنةً بالتسميات الصحيحة"""
    Y = labels.numpy()
    Yhat = np.argmax(outputs, axis=1)
    return float(np.sum(Yhat == Y))

المتغير outputs عبارة عن قائمة باحتمالات الصنف لكل عينة. على سبيل المثال، قد تكون مخرجات عينة واحدة هي [0.1، 0.3، 0.4، 0.2]. المتغير labels عبارة عن قائمة تحمل التسميات المقابلة للعينات. على سبيل المثال، قد تكون التسمية من أجل عينة واحدة هي 3.

يحوّل السطر ()Y = labels.numpy التسميات إلى مصفوفة نمباي. بينما يحوّل السطر الذي يليه مباشرةً القيم الاحتمالية من أجل كل عينة إلى قيمة عددية واحدة تقابل فهرس أكبر قيمة احتمالية من أجل تلك العينة. على سبيل المثال، إذا كانت قائمة الاحتمالات من أجل عينة واحدة هي [0.1، 0.3، 0.4، 0.2] فإنها يحولها إلى القيمة 2، لأن قيمة الفهرس 2 البالغة 0.4 هي أكبر قيمة.

نظرًا لأن كلا من Y و Yhat أصبحا الآن يشيران إلى صنفين، فيمكنك مقارنتهما. يتحقق Yhat == Y مما إذا كانت التسمية المتوقعة تُطابق التسمية الصحيحة، ويعيد قائمة كل عنصر فيها إما True أو False من أجل كل عينة. أخيرًا ((float(np.sum(Yhat == Y تحسب عدد حالات التطابق بين Y و Yhat (أي كم مرة حصلنا على True).

أضف الدالة الثانية batch_evaluate، والتي تُطبّق الدالة evaluate على جميع الصور:

def batch_evaluate(
        net: Net,
        dataloader: torch.utils.data.DataLoader) -> float:
    """تقييم الشبكة العصبية على دفعات، إذا كانت مجموعة البيانات كبيرة جدًا"""
    score = n = 0.0
    for batch in dataloader:
        n += len(batch['image'])
        outputs = net(batch['image'])
        if isinstance(outputs, torch.Tensor):
            outputs = outputs.detach().numpy()
        score += evaluate(outputs, batch['label'][:, 0])
    return score / n

الدفعة batch عبارة عن مجموعة من الصور المخزنة كموتر واحد. أولاً، نزيد إجمالي عدد الصور التي نُقيّمها n بعدد الصور في هذه الدفعة من خلال التعليمة
([n += len(batch['image. ثم نُنفّذ عملية الاستدلال على الشبكة العصبية باستخدام مجموعة من الصور. ونُجري عملية التحقق من النوع بالتعليمة
(if isinstance(outputs; torch.Tensor  فإذا كان نوع outputs هو torch.Tensor نحول outputs إلى مصفوفة نمباي.
أخيرًا، يمكنك استخدام evaluate لحساب عدد العينات المُصنّفة بطريقة صحيحة، وفي ختام الدالة يمكنك حساب النسبة المئوية للعينات التي صنفتها بطريقة صحيحة من خلال التعليمة evaluate.

أخيرًا، أضف البرنامج التالي للاستفادة من الأدوات المساعدة السابقة:

def validate():
    trainloader, testloader = get_train_test_loaders()
    net = Net().float()

    pretrained_model = torch.load("checkpoint.pth")
    net.load_state_dict(pretrained_model)

    print('=' * 10, 'PyTorch', '=' * 10)
    train_acc = batch_evaluate(net, trainloader) * 100.
    print('Training accuracy: %.1f' % train_acc)
    test_acc = batch_evaluate(net, testloader) * 100.
    print('Validation accuracy: %.1f' % test_acc)


if __name__ == '__main__':
    validate()

من خلال هذه الشيفرة، تجري عمليّة تحميل الشبكة العصبية المُدرّبة مسبقًا وتقييم أدائها على مجموعة بيانات لغة الإشارة المتوفرة. على وجه التحديد، يطبع البرنامج دقة النموذج في توقع أصناف الصور التي المُستخدمة للتدريب، كما أنه يطبع دقة النموذج في توقع أصناف الصور التي لم يجري استخدامها أثناء عملية التدريب، وهي مجموعة منفصلة من الصور التي تضعها جانبًا لأغراض الاختبار، وتسمى مجموعة التحقق.

نُصدّر بعد ذلك نموذج بايتورش إلى ملف ONNX ثنائي. يمكن بعد ذلك استخدام هذا الملف الثنائي في الإنتاج لتنفيذ عمليات الاستدلال مع النموذج الخاص بك. والأهم من ذلك، أن الشيفرة التي تُشغّل هذا الثنائي لا تحتاج إلى نسخة من تعريف الشبكة الأصلي. في نهاية دالة validate، أضف ما يلي:

trainloader, testloader = get_train_test_loaders(1)

    # export to onnx
    fname = "signlanguage.onnx"
    dummy = torch.randn(1, 1, 28, 28)
    torch.onnx.export(net, dummy, fname, input_names=['input'])

    # check exported model
    model = onnx.load(fname)
    onnx.checker.check_model(model)  # check model is well-formed

    # create runnable session with exported model
    ort_session = ort.InferenceSession(fname)
    net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0]

    print('=' * 10, 'ONNX', '=' * 10)
    train_acc = batch_evaluate(net, trainloader) * 100.
    print('Training accuracy: %.1f' % train_acc)
    test_acc = batch_evaluate(net, testloader) * 100.
    print('Validation accuracy: %.1f' % test_acc)

يؤدي هذا إلى تصدير نموذج ONNX والتحقق من النموذج الذي جرى تصديره، ثم تشغيل الاستدلال على النموذج الذي صُدّر. تحقق جيدًا من تطابق ملفك مع ملف الخطوة 4 الموجود في هذا المستودع:

from torch.utils.data import Dataset
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch
import numpy as np

import onnx
import onnxruntime as ort

from step_2_dataset import get_train_test_loaders
from step_3_train import Net


def evaluate(outputs: Variable, labels: Variable) -> float:
    """تقييم جودة توقعات النموذج، مقارنةً بالتسميات الصحيحة"""
    Y = labels.numpy()
    Yhat = np.argmax(outputs, axis=1)
    return float(np.sum(Yhat == Y))


def batch_evaluate(
        net: Net,
        dataloader: torch.utils.data.DataLoader) -> float:
    """تقييم الشبكة العصبية على دفعات، إذا كانت مجموعة البيانات كبيرة جدًا"""
    score = n = 0.0
    for batch in dataloader:
        n += len(batch['image'])
        outputs = net(batch['image'])
        if isinstance(outputs, torch.Tensor):
            outputs = outputs.detach().numpy()
        score += evaluate(outputs, batch['label'][:, 0])
    return score / n


def validate():
    trainloader, testloader = get_train_test_loaders()
    net = Net().float().eval()

    pretrained_model = torch.load("checkpoint.pth")
    net.load_state_dict(pretrained_model)

    print('=' * 10, 'PyTorch', '=' * 10)
    train_acc = batch_evaluate(net, trainloader) * 100.
    print('Training accuracy: %.1f' % train_acc)
    test_acc = batch_evaluate(net, testloader) * 100.
    print('Validation accuracy: %.1f' % test_acc)

    trainloader, testloader = get_train_test_loaders(1)

    # export to onnx
    fname = "signlanguage.onnx"
    dummy = torch.randn(1, 1, 28, 28)
    torch.onnx.export(net, dummy, fname, input_names=['input'])

    # check exported model
    model = onnx.load(fname)
    onnx.checker.check_model(model)  # check model is well-formed

    # create runnable session with exported model
    ort_session = ort.InferenceSession(fname)
    net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0]

    print('=' * 10, 'ONNX', '=' * 10)
    train_acc = batch_evaluate(net, trainloader) * 100.
    print('Training accuracy: %.1f' % train_acc)
    test_acc = batch_evaluate(net, testloader) * 100.
    print('Validation accuracy: %.1f' % test_acc)


if __name__ == '__main__':
    validate()

لاستخدام نقطة التحقق checkpoint من الخطوة الأخيرة وتقييمها، شغّل ما يلي:

(signlanguage) $ python step_4_evaluate.py

سيؤدي هذا إلى الحصول على مخرجات مشابهة لما يلي، مما يؤكد أن النموذج الذي صدرته لا يعمل فحسب، بل يتوافق أيضًا مع نموذج بايتورش الأصلي الخاص
بك أيضًا:

========== PyTorch ==========
Training accuracy: 99.9
Validation accuracy: 97.4
========== ONNX ==========
Training accuracy: 99.9
Validation accuracy: 97.4

حققت شبكتك العصبية دقة تصل إلى 99.9% على بيانات التدريب ودقة تبلغ 97.4% على بيانات التحقق. تُشير هذه الفجوة بين دقة التدريب والتحقق إلى أن النموذج الخاص بك أفرط في ضبط قيم معلماته على بيانات التدريب، وهذا ماندعوه بالضبط الزائد أو الإفراط في المُلائمة Overfitting. هذا يعني أنه بدلاً من تعلم الأنماط التي يمكن تعميمها على جميع الصور (بما في ذلك صور العالم الحقيقي) ، فإن النموذج الخاص بك تعلّم أنماطًا خاصة للغاية ببيانات التدريب، أي وكأنه حفظ البيانات ولم يفهمها (حافظ مش فاهم).

في هذه المرحلة، أكملنا بناء مُصنّف لغة الإشارة. يمكن لنموذجنا أن يُميّز الإشارات بطريقة صحيحة طوال الوقت تقريبًا. هذا نموذج جيد إلى حد معقول، لذلك ننتقل إلى المرحلة النهائية من تطبيقنا. سوف نستخدم مصنف لغة الإشارة هذا في تطبيق كاميرا الويب في الزمن الحقيقي.

الخطوة 5 -الربط مع الكاميرا

هدفك التالي هو ربط كاميرا الحاسوب بمصنف لغة الإشارة الخاص بك. يتضمن ذلك التقاط المُدخلات من الكاميرا ثم تصنيف لغة الإشارة المعروضة ثم عرض معنى الإشارة للمستخدم.

أنشئ الآن برنامجًا بلغة بايثون لاكتشاف الوجه. أنشئ الملف step_6_camera.py باستخدام المحرر nano أو محرر النصوص المفضل لديك:

(signlanguage) $ nano step_5_camera.py

أضف الشيفرة التالية إليه:

"""اختبار تصنيف لغة الإشارة"""
import cv2
import numpy as np
import onnxruntime as ort

def main():
    pass

if __name__ == '__main__':
    main()

تستورد هذا الشيفرة مكتبة OpenCV المُشار إليها بالاسم cv2. تحتوي هذه المكتبة على العديد من الأدوات اللازمة لمعالجة الصور. تستورد أيضًا onnxruntime وهي كل ما تحتاجه لتشغيل الاستدلال على النموذج الخاص بك. ما تبقى من التعليمات البرمجية هو البنية المبدأية لبرنامج بايثون الذي سنكتبه.

الآن استبدل pass في الدالة main بالشيفرة التالية، التي تُهيئ مُصنف لغة الإشارة بالمعلمات التي دربنا النموذج معها مسبقًا. أربط أيضًا الفهارس بالحروف وحدد الإحصائيات المتعلقة بالمعالجة المسبقة للصور:

def main():
    # الثوابت
    index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY')
    mean = 0.485 * 255.
    std = 0.229 * 255.

    # إنشاء جلسة قابلة للتشغيل باستخدام النموذج المُصدَّر
    ort_session = ort.InferenceSession("signlanguage.onnx")

ستستخدم عناصر من برنامج اختبار موجودة في وثائق OpenCV الرسمية، بالتالي ستحتاج إلى تحديث شيفرة الدالة main. ابدأ بتهيئة كائن VideoCapture الذي يلتقط البث المباشر من كاميرا حاسوبك. ضع ذلك في نهاية الدالة main:

def main():
    ...
    # إنشاء جلسة قابلة للتشغيل باستخدام النموذج المُصدَّر
    ort_session = ort.InferenceSession("signlanguage.onnx")

    cap = cv2.VideoCapture(0)

ثم أضف حلقة while لتقرأ من الكاميرا في كل خطوة زمنية:

def main():
    ...
    cap = cv2.VideoCapture(0)
    while True:
        # التقط إطارًا بإطار
        ret, frame = cap.read()

اكتب دالة مساعدة للاحتفاظ بالمنطقة الوسطى فقط مع التخلص من المناطق المحيطة من إطار الكاميرا. ضع هذه الدالة قبل الدالة main:

def center_crop(frame):
    h, w, _ = frame.shape
    start = abs(h - w) // 2
    if h > w:
        frame = frame[start: start + w]
    else:
        frame = frame[:, start: start + h]
    return frame

خذ الآن المنطقة الوسطى لإطار الكاميرا، وحوّلها إلى صورة رماديّة grayscale وطبق عمليّة تقييس عليها وعدّل حجمها إلى 28 × 28. ضع هذا داخل الحلقة while داخل main:

def main():
    ...
    while True:
        # التقط إطارًا بإطار
        ret, frame = cap.read()

        # معالجة البيانات
        frame = center_crop(frame)
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
        x = cv2.resize(frame, (28, 28))
        x = (frame - mean) / std

مازلنا ضمن حلقة while شغّل الاستدلال الآن باستخدام onnxruntime. وحوّل المخرجات إلى فهرس صنف، ثم إلى حرف:

...
        x = (frame - mean) / std

        x = x.reshape(1, 1, 28, 28).astype(np.float32)
        y = ort_session.run(None, {'input': x})[0]

        index = np.argmax(y, axis=1)
        letter = index_to_letter[int(index)]

اعرض الحرف المتوقع داخل الإطار، ثم اعرض الإطار مرة أخرى للمستخدم:

...
        letter = index_to_letter[int(index)]

        cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2)
        cv2.imshow("Sign Language Translator", frame)

في نهاية الحلقة، أضف هذه الشيفرة للتحقق مما إذا كان المستخدم قد وصل إلى الحرف q، وإذا كان الأمر كذلك، نُغلق التطبيق. نستخدم cv2.waitKey من أجل الانتظار لمدة 1 مللي ثانية. أضف ما يلي:

...
        cv2.imshow("Sign Language Translator", frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

أخيرًا، حرّر الكاميرا وأغلق جميع النوافذ. وضع هذا الكود خارج الحلقة while لإنهاء main:

...

    while True:
        ...
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break


    cap.release()
    cv2.destroyAllWindows()

تحقق جيدًا من تطابق ملفك مع الشيفرة التالية أو هذا المستودع:

import cv2
import numpy as np
import onnxruntime as ort


def center_crop(frame):
    h, w, _ = frame.shape
    start = abs(h - w) // 2
    if h > w:
        return frame[start: start + w]
    return frame[:, start: start + h]


def main():
    # الثوابت
    index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY')
    mean = 0.485 * 255.
    std = 0.229 * 255.

    # إنشاء جلسة قابلة للتشغيل باستخدام النموذج المُصدَّر
    ort_session = ort.InferenceSession("signlanguage.onnx")

    cap = cv2.VideoCapture(0)
    while True:
        # التقط إطارًا بإطار
        ret, frame = cap.read()

        # معالجة البيانات
        frame = center_crop(frame)
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
        x = cv2.resize(frame, (28, 28))
        x = (x - mean) / std

        x = x.reshape(1, 1, 28, 28).astype(np.float32)
        y = ort_session.run(None, {'input': x})[0]

        index = np.argmax(y, axis=1)
        letter = index_to_letter[int(index)]

        cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2)
        cv2.imshow("Sign Language Translator", frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

اخرج من الملف وشغّل البرنامج:

(signlanguage) $ python step_5_camera.py

بمجرد تشغيل البرنامج، ستظهر نافذة تحتوي على البث المباشر لكاميرا الويب الخاصة بك. يُعرض حرف لغة الإشارة المتوقع في أعلى اليسار. ارفع يدك وضع إشارتك المُفضلة لترى عمل المُصنّف الخاص بك. فيما يلي بعض النتائج النموذجية التي توضّح الحرفين L و D.

image 1

image 2

لاحظ أن الخلفية أثناء الاختبار يجب أن تكون واضحة إلى حد ما حتى يعمل هذا النموذج. سبب ذلك هو عدم التنوّع في الخلفيات الموجودة في الصور في مجموعة البيانات التي درّبنا عليها النموذج. لو كانت مجموعة البيانات تتضمن صورًا لإشارات يد ذات خلفيات متنوعة، لكانت الشبكة متينة في مواجهة الخلفيات المتنوعة التي قد تحتوي ضوضاء أو اختلافات. عمومًا، تتميز مجموعة البيانات بخلفيات فارغة وأيادي متمركزة بطريقة جيد. نتيجة لذلك، فإن أداء نموذجنا يكون مثاليًا عندما تكون يدك في المنتصف وخلفها خلفية فارغة.

وبهذا ينتهي تطبيق مُترجم لغة الإشارة.

خاتمة

لقد بنينا في مقال اليوم مُترجمًا للغة الإشارة الأمريكية باستخدام الرؤية الحاسوبية ونموذج تعلّم آلي. ورأيت جوانب جديدة لتدريب نموذج التعلّم الآلي، مثل زيادة البيانات من أجل متانة النموذج وجداول معدّل التعلّم لتقليل الخسارة وتصدير نماذج الذكاء الاصطناعي باستخدام أونكس لاستخدامها في الإنتاج. وقد توّجنا ذلك بإنشاء تطبيق رؤية حاسوبية في الزمن الحقيقي، والذي يُترجم لغة الإشارة إلى أحرف باستخدام خط معالجة أنشأناه. تجدر الإشارة إلى أن مكافحة هشاشة المُصنف النهائي يمكن معالجتها بأي من الطرق التالية أو جميعها. لمزيد من المعرفة، جرّب المواضيع التالية لتحسين هشاشة المُصنّف:

  • التعميم Generalization: هذا ليس موضوعًا فرعيًا ضمن مجال الرؤية الحاسوبية، بل هو مشكلة موجودة في جميع مراحل تعلّم الآلة.
  • التكيف مع المجال Domain Adaptation: لنفترض أن النموذج الخاص بك قد تدرّب في المجال A (على سبيل المثال، البيئات المشمسة). هل يمكنك تكييف النموذج مع المجال B (على سبيل المثال، البيئات الملبّدة بالغيوم) بسرعة؟
  • أمثلة مخادعة Adversarial Examples: لنفترض أن الخصم يصمم صورًا عمدًا لخداع نموذجك. كيف يمكنك تصميم مثل هذه الصور؟ وكيف يمكنك مكافحتها والتعامل معها؟

ترجمة -وبتصرُّف- للمقال How To Build a Neural Network to Translate Sign Language into English لصاحبه Alvin Wan.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...