كتابة مجموعات البيانات المخصصة

اتبع هذا الدليل لإنشاء مجموعة بيانات جديدة (إما في TFDS أو في مستودعك الخاص).

تحقق من قائمة مجموعات البيانات لدينا لمعرفة ما إذا كانت مجموعة البيانات التي تريدها موجودة بالفعل.

باختصار شديد

أسهل طريقة لكتابة مجموعة بيانات جديدة هي استخدام واجهة سطر الأوامر TFDS :

cd path/to/my/project/datasets/
tfds new my_dataset  # Create `my_dataset/my_dataset.py` template files
# [...] Manually modify `my_dataset/my_dataset_dataset_builder.py` to implement your dataset.
cd my_dataset/
tfds build  # Download and prepare the dataset to `~/tensorflow_datasets/`

لاستخدام مجموعة البيانات الجديدة مع tfds.load('my_dataset') :

  • سيقوم tfds.load تلقائيًا باكتشاف وتحميل مجموعة البيانات التي تم إنشاؤها في ~/tensorflow_datasets/my_dataset/ (على سبيل المثال بواسطة tfds build ).
  • بدلاً من ذلك، يمكنك import my.project.datasets.my_dataset بشكل صريح لتسجيل مجموعة البيانات الخاصة بك:
import my.project.datasets.my_dataset  # Register `my_dataset`

ds = tfds.load('my_dataset')  # `my_dataset` registered

ملخص

تُوزّع مجموعات البيانات بأنواعٍ مختلفة من التنسيقات وفي أماكنَ مُتعددة، ولا تُخزّن دائمًا بتنسيقٍ جاهزٍ للاستخدام في مسار تعلّم الآلة. هنا يأتي دور TFDS.

يقوم TFDS بمعالجة مجموعات البيانات هذه وتحويلها إلى تنسيق قياسي (بيانات خارجية -> ملفات مُسلسلة)، والتي يمكن تحميلها بعد ذلك كخط أنابيب للتعلم الآلي (ملفات مُسلسلة -> tf.data.Dataset ). تتم عملية التسلسل مرة واحدة فقط، حيث يقرأ الوصول اللاحق من هذه الملفات المُعالجة مسبقًا مباشرةً.

تتم معظم عمليات المعالجة المسبقة تلقائيًا. كل مجموعة بيانات تُنفذ فئة فرعية من tfds.core.DatasetBuilder ، والتي تحدد ما يلي:

  • من أين تأتي البيانات (أي عناوين URL الخاصة بها)؛
  • كيف تبدو مجموعة البيانات (أي خصائصها)؛
  • كيفية تقسيم البيانات (مثلاً: TRAIN TEST
  • والأمثلة الفردية في مجموعة البيانات.

اكتب مجموعة البيانات الخاصة بك

القالب الافتراضي: tfds new

استخدم واجهة سطر الأوامر TFDS لإنشاء ملفات بايثون القالبية المطلوبة.

cd path/to/project/datasets/  # Or use `--dir=path/to/project/datasets/` below
tfds new my_dataset

سيؤدي هذا الأمر إلى إنشاء مجلد جديد باسم my_dataset/ بالبنية التالية:

my_dataset/
    __init__.py
    README.md # Markdown description of the dataset.
    CITATIONS.bib # Bibtex citation for the dataset.
    TAGS.txt # List of tags describing the dataset.
    my_dataset_dataset_builder.py # Dataset definition
    my_dataset_dataset_builder_test.py # Test
    dummy_data/ # (optional) Fake data (used for testing)
    checksum.tsv # (optional) URL checksums (see `checksums` section).

ابحث عن TODO(my_dataset) هنا وقم بالتعديل وفقًا لذلك.

مثال على مجموعة البيانات

جميع مجموعات البيانات عبارة عن فئات فرعية مُنفذة من tfds.core.DatasetBuilder ، الذي يتولى معظم العمليات الروتينية. وهو يدعم ما يلي:

فيما يلي مثال بسيط لمنشئ مجموعة بيانات يعتمد على tfds.core.GeneratorBasedBuilder :

class Builder(tfds.core.GeneratorBasedBuilder):
  """DatasetBuilder for my_dataset dataset."""

  VERSION = tfds.core.Version('1.0.0')
  RELEASE_NOTES = {
      '1.0.0': 'Initial release.',
  }

  def _info(self) -> tfds.core.DatasetInfo:
    """Dataset metadata (homepage, citation,...)."""
    return self.dataset_info_from_configs(
        features=tfds.features.FeaturesDict({
            'image': tfds.features.Image(shape=(256, 256, 3)),
            'label': tfds.features.ClassLabel(
                names=['no', 'yes'],
                doc='Whether this is a picture of a cat'),
        }),
    )

  def _split_generators(self, dl_manager: tfds.download.DownloadManager):
    """Download the data and define splits."""
    extracted_path = dl_manager.download_and_extract('http://data.org/data.zip')
    # dl_manager returns pathlib-like objects with `path.read_text()`,
    # `path.iterdir()`,...
    return {
        'train': self._generate_examples(path=extracted_path / 'train_images'),
        'test': self._generate_examples(path=extracted_path / 'test_images'),
    }

  def _generate_examples(self, path) -> Iterator[Tuple[Key, Example]]:
    """Generator of examples for each split."""
    for img_path in path.glob('*.jpeg'):
      # Yields (key, example)
      yield img_path.name, {
          'image': img_path,
          'label': 'yes' if img_path.name.startswith('yes_') else 'no',
      }

لاحظ أنه بالنسبة لبعض تنسيقات البيانات المحددة، فإننا نوفر أدوات إنشاء مجموعات البيانات الجاهزة للاستخدام لتولي معظم عمليات معالجة البيانات.

دعونا نرى بالتفصيل الطرق المجردة الثلاث للكتابة فوق البيانات.

_info : بيانات تعريف مجموعة البيانات

_info تُرجع tfds.core.DatasetInfo التي تحتوي على بيانات تعريف مجموعة البيانات .

def _info(self):
  # The `dataset_info_from_configs` base method will construct the
  # `tfds.core.DatasetInfo` object using the passed-in parameters and
  # adding: builder (self), description/citations/tags from the config
  # files located in the same package.
  return self.dataset_info_from_configs(
      homepage='https://dataset-homepage.org',
      features=tfds.features.FeaturesDict({
          'image_description': tfds.features.Text(),
          'image': tfds.features.Image(),
          # Here, 'label' can be 0-4.
          'label': tfds.features.ClassLabel(num_classes=5),
      }),
      # If there's a common `(input, target)` tuple from the features,
      # specify them here. They'll be used if as_supervised=True in
      # builder.as_dataset.
      supervised_keys=('image', 'label'),
      # Specify whether to disable shuffling on the examples. Set to False by default.
      disable_shuffling=False,
  )

معظم الحقول ينبغي أن تكون واضحة بذاتها. بعض التوضيحات:

كتابة ملف BibText CITATIONS.bib :

  • ابحث في موقع مجموعة البيانات عن تعليمات الاستشهاد (استخدم ذلك بتنسيق BibTeX).
  • بالنسبة لأوراق arXiv : ابحث عن الورقة وانقر على رابط BibText الموجود على الجانب الأيمن.
  • ابحث عن الورقة البحثية على Google Scholar وانقر على علامة الاقتباس المزدوجة أسفل العنوان، ثم انقر على BibTeX في النافذة المنبثقة.
  • إذا لم تكن هناك ورقة بحثية مرتبطة (على سبيل المثال، يوجد موقع ويب فقط)، يمكنك استخدام محرر BibTeX عبر الإنترنت لإنشاء إدخال BibTeX مخصص (تحتوي القائمة المنسدلة على نوع إدخال Online ).

تحديث ملف TAGS.txt :

  • جميع العلامات المسموح بها مُعبأة مسبقًا في الملف المُنشأ.
  • قم بإزالة جميع العلامات التي لا تنطبق على مجموعة البيانات.
  • يتم سرد العلامات الصالحة في tensorflow_datasets/core/valid_tags.txt .
  • لإضافة وسم إلى تلك القائمة، يرجى إرسال طلب سحب.

حافظ على ترتيب قاعدة البيانات

بشكل افتراضي، تُخلط سجلات مجموعات البيانات عند تخزينها لجعل توزيع الفئات أكثر تجانسًا، نظرًا لأن السجلات التي تنتمي إلى الفئة نفسها غالبًا ما تكون متجاورة. لتحديد فرز مجموعة البيانات حسب المفتاح المُولّد بواسطة _generate_examples ، يجب ضبط حقل disable_shuffling على True . القيمة الافتراضية هي False .

def _info(self):
  return self.dataset_info_from_configs(
    # [...]
    disable_shuffling=True,
    # [...]
  )

ضع في اعتبارك أن تعطيل عملية الخلط يؤثر على الأداء حيث لا يمكن قراءة الأجزاء بالتوازي بعد الآن.

_split_generators : يقوم بتنزيل البيانات وتقسيمها

تنزيل واستخراج البيانات المصدرية

تتطلب معظم مجموعات البيانات تنزيل البيانات من الإنترنت. ويتم ذلك باستخدام وسيطة الإدخال tfds.download.DownloadManager الخاصة بـ _split_generators . يحتوي dl_manager على الطرق التالية:

  • download : يدعم http(s):// ftp(s)://
  • extract : يدعم حاليًا ملفات .zip و .gz و .tar .
  • download_and_extract : نفس وظيفة dl_manager.extract(dl_manager.download(urls))

تُرجع جميع هذه الطرق tfds.core.Path (أسماء بديلة لـ epath.Path )، وهي كائنات تشبه pathlib.Path .

تدعم هذه الطرق بنية متداخلة عشوائية ( list ، dict )، مثل:

extracted_paths = dl_manager.download_and_extract({
    'foo': 'https://example.com/foo.zip',
    'bar': 'https://example.com/bar.zip',
})
# This returns:
assert extracted_paths == {
    'foo': Path('/path/to/extracted_foo/'),
    'bar': Path('/path/extracted_bar/'),
}

التنزيل والاستخراج اليدوي

بعض البيانات لا يمكن تنزيلها تلقائيًا (على سبيل المثال، تتطلب تسجيل الدخول)، في هذه الحالة، سيقوم المستخدم بتنزيل بيانات المصدر يدويًا ووضعها في manual_dir/ (الوضع الافتراضي هو ~/tensorflow_datasets/downloads/manual/ ).

يمكن بعد ذلك الوصول إلى الملفات من خلال dl_manager.manual_dir :

class MyDataset(tfds.core.GeneratorBasedBuilder):

  MANUAL_DOWNLOAD_INSTRUCTIONS = """
  Register into https://example.org/login to get the data. Place the `data.zip`
  file in the `manual_dir/`.
  """

  def _split_generators(self, dl_manager):
    # data_path is a pathlib-like `Path('<manual_dir>/data.zip')`
    archive_path = dl_manager.manual_dir / 'data.zip'
    # Extract the manually downloaded `data.zip`
    extracted_path = dl_manager.extract(archive_path)
    ...

يمكن تخصيص موقع manual_dir باستخدام tfds build --manual_dir= أو باستخدام tfds.download.DownloadConfig .

اقرأ الأرشيف مباشرة

تقوم dl_manager.iter_archive بقراءة الأرشيفات بشكل تسلسلي دون استخراجها. وهذا من شأنه أن يوفر مساحة تخزين ويحسن الأداء على بعض أنظمة الملفات.

for filename, fobj in dl_manager.iter_archive('path/to/archive.zip'):
  ...

يحتوي fobj على نفس الطرق المستخدمة with open('rb') as fobj: (على سبيل المثال fobj.read() )

تحديد تقسيمات مجموعة البيانات

إذا كانت مجموعة البيانات تتضمن تقسيمات مُحددة مسبقًا (مثلًا، مجموعة بيانات MNIST تتضمن تقسيمات train test )، فاحتفظ بها. وإلا، فحدد تقسيمًا واحدًا فقط باسم " all ". يمكن للمستخدمين إنشاء تقسيمات فرعية خاصة بهم ديناميكيًا باستخدام واجهة برمجة تطبيقات التقسيم الفرعي (مثلًا: split='train[80%:]' ). لاحظ أنه يمكن استخدام أي سلسلة أبجدية كاسم للتقسيم، باستثناء all المذكور سابقًا.

def _split_generators(self, dl_manager):
  # Download source data
  extracted_path = dl_manager.download_and_extract(...)

  # Specify the splits
  return {
      'train': self._generate_examples(
          images_path=extracted_path / 'train_imgs',
          label_path=extracted_path / 'train_labels.csv',
      ),
      'test': self._generate_examples(
          images_path=extracted_path / 'test_imgs',
          label_path=extracted_path / 'test_labels.csv',
      ),
  }

_generate_examples : مولد الأمثلة

تقوم الدالة _generate_examples بإنشاء الأمثلة لكل قسم من البيانات المصدرية.

ستقوم هذه الطريقة عادةً بقراءة عناصر مجموعة البيانات المصدرية (مثل ملف CSV) وستنتج أزواجًا (key, feature_dict) :

  • key : مُعرِّف المثال. يُستخدم لخلط الأمثلة بشكل حتمي باستخدام hash(key) أو لفرزها حسب المفتاح عند تعطيل الخلط (انظر قسم الحفاظ على ترتيب مجموعة البيانات ). يجب أن يكون:
    • فريد : إذا استخدم مثالان نفس المفتاح، فسيتم طرح استثناء.
    • حتمية : لا ينبغي أن تعتمد على download_dir أو os.path.listdir ترتيبها، إلخ. يجب أن يؤدي توليد البيانات مرتين إلى نفس المفتاح.
    • قابل للمقارنة : إذا تم تعطيل خاصية الخلط، فسيتم استخدام المفتاح لفرز مجموعة البيانات.
  • feature_dict : dict يحتوي على قيم المثال.
    • يجب أن يتطابق الهيكل مع هيكل features= المحدد في tfds.core.DatasetInfo .
    • سيتم ترميز أنواع البيانات المعقدة (الصور، الفيديو، الصوت، ...) تلقائيًا.
    • تقبل كل ميزة عادةً أنواع إدخال متعددة (على سبيل المثال، تقبل ميزة الفيديو /path/to/vid.mp4 ، و np.array(shape=(l, h, w, c)) ، و List[paths] ، و List[np.array(shape=(h, w, c)] ، و List[img_bytes] ، ...).
    • راجع دليل موصل الميزات لمزيد من المعلومات.
def _generate_examples(self, images_path, label_path):
  # Read the input data out of the source files
  with label_path.open() as f:
    for row in csv.DictReader(f):
      image_id = row['image_id']
      # And yield (key, feature_dict)
      yield image_id, {
          'image_description': row['description'],
          'image': images_path / f'{image_id}.jpeg',
          'label': row['label'],
      }

الوصول إلى الملفات و tf.io.gfile

من أجل دعم أنظمة التخزين السحابي، تجنب استخدام عمليات الإدخال/الإخراج المدمجة في بايثون.

بدلاً من ذلك، يقوم dl_manager بإرجاع كائنات شبيهة بـ pathlib متوافقة مباشرة مع تخزين Google Cloud:

path = dl_manager.download_and_extract('http://some-website/my_data.zip')

json_path = path / 'data/file.json'

json.loads(json_path.read_text())

أو بدلاً من ذلك، استخدم واجهة برمجة التطبيقات tf.io.gfile بدلاً من الواجهة المدمجة لعمليات الملفات:

يُفضل استخدام Pathlib بدلاً من tf.io.gfile (انظر rational) .

تبعيات إضافية

تتطلب بعض مجموعات البيانات تبعيات إضافية في بايثون فقط أثناء عملية الإنشاء. على سبيل المثال، تستخدم مجموعة بيانات SVHN مكتبة scipy لتحميل بعض البيانات.

إذا كنت تضيف مجموعة بيانات إلى مستودع TFDS، فيُرجى استخدام tfds.core.lazy_imports للحفاظ على حجم حزمة tensorflow-datasets صغيرًا. سيقوم المستخدمون بتثبيت التبعيات الإضافية عند الحاجة فقط.

لاستخدام lazy_imports :

  • أضف مدخلاً لمجموعة البيانات الخاصة بك إلى DATASET_EXTRAS في setup.py . هذا يُمكّن المستخدمين من تنفيذ الأمر، على سبيل المثال، pip install 'tensorflow-datasets[svhn]' لتثبيت التبعيات الإضافية.
  • أضف إدخالًا لعملية الاستيراد الخاصة بك إلى LazyImporter وإلى LazyImportsTest .
  • استخدم tfds.core.lazy_imports للوصول إلى التبعية (على سبيل المثال، tfds.core.lazy_imports.scipy ) في DatasetBuilder الخاص بك.

بيانات تالفة

بعض مجموعات البيانات ليست نظيفة تمامًا وتحتوي على بيانات تالفة (على سبيل المثال، الصور بصيغة JPEG ولكن بعضها غير صالح). يجب حذف هذه الأمثلة، مع ذكر عدد الأمثلة المحذوفة وسبب حذفها في وصف مجموعة البيانات.

تكوين/متغيرات مجموعة البيانات (tfds.core.BuilderConfig)

قد تحتوي بعض مجموعات البيانات على عدة متغيرات، أو خيارات لكيفية معالجة البيانات مسبقًا وكتابتها على القرص. على سبيل المثال، تحتوي مجموعة بيانات cycle_gan على إعداد واحد لكل زوج من الكائنات ( cycle_gan/horse2zebra ، cycle_gan/monet2photo ، ...).

يتم ذلك من خلال tfds.core.BuilderConfig s:

  1. قم بتعريف كائن التكوين الخاص بك كفئة فرعية من tfds.core.BuilderConfig . على سبيل المثال، MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. قم بتعريف عضو الفئة BUILDER_CONFIGS = [] في MyDataset الذي يسرد MyDatasetConfig التي تعرضها مجموعة البيانات.

    class MyDataset(tfds.core.GeneratorBasedBuilder):
      VERSION = tfds.core.Version('1.0.0')
      # pytype: disable=wrong-keyword-args
      BUILDER_CONFIGS = [
          # `name` (and optionally `description`) are required for each config
          MyDatasetConfig(name='small', description='Small ...', img_size=(8, 8)),
          MyDatasetConfig(name='big', description='Big ...', img_size=(32, 32)),
      ]
      # pytype: enable=wrong-keyword-args
    
  3. استخدم self.builder_config في MyDataset لضبط إعدادات إنشاء البيانات (مثل shape=self.builder_config.img_size ). قد يشمل ذلك تعيين قيم مختلفة في _info() أو تغيير الوصول إلى بيانات التنزيل.

ملحوظات:

  • لكل إعداد اسم فريد. الاسم الكامل للإعداد هو dataset_name/config_name (على سبيل المثال coco/2017 ).
  • في حال عدم تحديد ذلك، سيتم استخدام أول إعداد في BUILDER_CONFIGS (على سبيل المثال، tfds.load('c4') القيمة الافتراضية هي c4/en ).

انظر إلى anli للحصول على مثال لمجموعة بيانات تستخدم BuilderConfig .

إصدار

قد يشير مصطلح "النسخة" إلى معنيين مختلفين:

  • إصدار البيانات الأصلي "الخارجي": على سبيل المثال COCO v2019، v2017،...
  • إصدار كود TFDS "الداخلي": على سبيل المثال، إعادة تسمية ميزة في tfds.features.FeaturesDict ، أو إصلاح خطأ في _generate_examples

لتحديث مجموعة البيانات:

  • لتحديث البيانات "الخارجية": قد يرغب عدة مستخدمين في الوصول إلى سنة/إصدار معين في وقت واحد. يتم ذلك باستخدام ملف tfds.core.BuilderConfig واحد لكل إصدار (مثل coco/2017 ، coco/2019 ) أو فئة واحدة لكل إصدار (مثل Voc2007 ، Voc2012 ).
  • بالنسبة لتحديثات التعليمات البرمجية "الداخلية": يقوم المستخدمون بتنزيل أحدث إصدار فقط. يجب أن يؤدي أي تحديث للتعليمات البرمجية إلى زيادة قيمة سمة VERSION (على سبيل المثال، من 1.0.0 إلى VERSION = tfds.core.Version('2.0.0') ) وفقًا للترقيم الدلالي للإصدارات .

أضف عملية استيراد للتسجيل

لا تنس استيراد وحدة مجموعة البيانات إلى __init__ الخاصة بمشروعك ليتم تسجيلها تلقائيًا في tfds.load و tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

ds = tfds.load('my_dataset')  # MyDataset available

على سبيل المثال، إذا كنت تساهم في tensorflow/datasets ، فأضف استيراد الوحدة إلى ملف __init__.py الخاص بالدليل الفرعي (على سبيل المثال image/__init__.py ).

تحقق من المشاكل الشائعة في التنفيذ

يرجى التحقق من المشاكل الشائعة في التنفيذ .

اختبر مجموعة البيانات الخاصة بك

قم بالتنزيل والتحضير: tfds build

لإنشاء مجموعة البيانات، قم بتشغيل الأمر tfds build من الدليل my_dataset/ :

cd path/to/datasets/my_dataset/
tfds build --register_checksums

بعض العلامات المفيدة للتطوير:

  • --pdb : الدخول إلى وضع تصحيح الأخطاء في حالة حدوث استثناء.
  • --overwrite : حذف الملفات الموجودة إذا تم إنشاء مجموعة البيانات بالفعل.
  • --max_examples_per_split : توليد أول X مثال فقط (القيمة الافتراضية هي 1)، بدلاً من مجموعة البيانات الكاملة.
  • --register_checksums : تسجيل قيم التحقق من عناوين URL التي تم تنزيلها. يجب استخدامها فقط أثناء التطوير.

راجع وثائق واجهة سطر الأوامر للاطلاع على القائمة الكاملة للعلامات.

مجموعات التحقق

يوصى بتسجيل مجموعات التحقق من البيانات الخاصة بك لضمان الحتمية، والمساعدة في التوثيق، ... يتم ذلك عن طريق إنشاء مجموعة البيانات باستخدام --register_checksums (انظر القسم السابق).

إذا كنت تقوم بنشر مجموعات البيانات الخاصة بك من خلال PyPI، فلا تنس تصدير ملفات checksums.tsv (على سبيل المثال في package_data الخاص بـ setup.py ).

اختبر مجموعة البيانات الخاصة بك بشكل مستقل

تُعدّ tfds.testing.DatasetBuilderTestCase TestCase أساسية لاختبار مجموعة البيانات بشكل كامل. وهي تستخدم "بيانات وهمية" كبيانات اختبار تحاكي بنية مجموعة البيانات المصدرية.

  • يجب وضع بيانات الاختبار في المجلد my_dataset/dummy_data/ ، ويجب أن تحاكي عناصر مجموعة البيانات المصدرية كما تم تنزيلها واستخراجها. يمكن إنشاؤها يدويًا أو تلقائيًا باستخدام برنامج نصي ( مثال على البرنامج النصي ).
  • تأكد من استخدام بيانات مختلفة في تقسيمات بيانات الاختبار الخاصة بك، حيث سيفشل الاختبار إذا تداخلت تقسيمات مجموعة البيانات الخاصة بك.
  • يجب ألا تحتوي بيانات الاختبار على أي مواد محمية بحقوق الطبع والنشر . في حال الشك، لا تقم بإنشاء البيانات باستخدام مواد من مجموعة البيانات الأصلية.
import tensorflow_datasets as tfds
from . import my_dataset_dataset_builder


class MyDatasetTest(tfds.testing.DatasetBuilderTestCase):
  """Tests for my_dataset dataset."""
  DATASET_CLASS = my_dataset_dataset_builder.Builder
  SPLITS = {
      'train': 3,  # Number of fake train example
      'test': 1,  # Number of fake test example
  }

  # If you are calling `download/download_and_extract` with a dict, like:
  #   dl_manager.download({'some_key': 'http://a.org/out.txt', ...})
  # then the tests needs to provide the fake output paths relative to the
  # fake data directory
  DL_EXTRACT_RESULT = {
      'name1': 'path/to/file1',  # Relative to my_dataset/dummy_data dir.
      'name2': 'file2',
  }


if __name__ == '__main__':
  tfds.testing.test_main()

قم بتشغيل الأمر التالي لاختبار مجموعة البيانات.

python my_dataset_test.py

أرسل لنا ملاحظاتك

نسعى باستمرار لتحسين آلية إنشاء مجموعات البيانات، ولكن لا يمكننا تحقيق ذلك إلا إذا كنا على دراية بالمشاكل. ما هي المشاكل أو الأخطاء التي واجهتها أثناء إنشاء مجموعة البيانات؟ هل كان هناك جزءٌ مُربك، أو لم يعمل من المحاولة الأولى؟

يرجى مشاركة ملاحظاتك على GitHub .