توليد النصوص باستخدام أداة Improv

توليد النصوص شبه متقدم

من قبل Bruno Dias

بفضل كرم داعمي PROCJAM على Kickstarter، أمكن لي القدوم لكتابة درس عن مبادئ Improv. هذا درس شبه متقدم، ويتطلب فهمه أن يكون القارئ بارعاً في محررات النصوص ولغة برمجة JavaScript وأدوات واجهة سطر الأوامر، ولكنني لن أخوض في أي شيء بالغ التعقيد (على ما أأمل).

مكتبة Improv هي أداة تمكن من توليد النصوص إجرائياً عبر استخدام ملفات تدعى ملفات الإملاء "grammar". ملفات الإملاء عبارة عن مجموعة من القواعد التي تعرّف كيفية كتابة نص من خلال تجميعه ضمن أجزاء أبسط. العائق الأساسي لاستخدام ملفات الإملاء أن صنعها مرهق؛ يجب أن تكتب باليد أو تولّد من وسيلة أخرى (كتوليدها من مجموعة من البيانات المنظمة). ولكن يمكن لملفات الإملاء أن تولّد نصوصاً ذات نظام منطقي ودون تغيّر، واستخدام ملفات الإملاء أسهل للفهم (وللحصول على نتائج جيدة) من سلسلات ماركوف markov chains أو النص المتنبئ predictive text أو الشبكات العصبونية neural networks أو أياً من تقنيات حقل "تعلم الآلة" الحديثة.

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

إنشاء مشروع مع Improv

نحن نحتاج بيئة يمكنها تشغيل JavaScript. وعملياً إن أكثر المشاريع التي سنريد استخدام Improv فيها هي في بيئات مشابهة للمتصفحات مثل Electron أو Cordova. ولكن يستطيع أيضاً Improv أن يعمل ضمن Node.js، وهو ما سنستخدمه في هذا الدرس من أجل تبسيط الأمور، سنحتاج Node بكل الأحوال لتنزيل نسخة من Improv عبر خدمة npm لإدارة وتحميل ملحقات node. لذا يمكنك إنشاء بيئة Improv بسيطة على حاسوب منزلٌ عليه Node.js (وباستخدام إما واجهة سطر الأوامر الافتراضية في Linux/MacOS أو Powershell على Windows) عبر كتابة التالي:

سوف يتذمر npm حول عدم وجود ملف package.json في مشروعك ولكنه سينزل ذلك الملف. قم الآن بإنشاء ملفي improv-tutorial.js و grammar.yaml في مجلد مشروعك.

هذا نص بسيط لتشغيل Improv:

Improv هي دالة إنشاء "constructor" محملة من مكتبة خارجية (لذا يجب أن تستخدم مع عبارة new) التي تنشأ وترجع كائناً مولّداً. تقوم بأخذ وسيطين "parameters": ملف إملائي وكائن آخر لضبط إعداد المولّد. يوجد الكثير من الخيارات هنا، لكن من أجل هذا الدرس سنستخدم فقط الفلترات filters وإعادة الدمج reincorporate; سأعود إلى معنى تلك العبارتين لاحقاً في الدرس.

الآن بمجرد كتابة node improv-tutorial.js يمكنك تشغيل هذا المشروع، ولكن إن قمت بذلك فسيظهر خطأً لأن ملف grammar.yaml ليس موجوداً بعد. لذا سنقوم بإنشائه الآن.

القواعد الإملائية

Improv هي مكتبة مكتوبة في JavaScript، لذا فإن بيانات ملفات الإملاء هي كائنات في JavaScript ولكن ذات هيكل طبقي عميق يمكن أن يكرر نفسه. لتسهيل قراءة الملف وحفظ صحتي العقلية، أحب أن أكتبهم باستخدام YAML بدلاً من JavaScript. ملف إملائي بسيط يبدو كالتالي

احفظ ذلك الملف باسم grammar.yaml ثم شغل النص مجدداً، يجب أن تحصل على هذا المنتج: "The HMS Invincible is a cutter commissioned in 1888."

قد تتيه مما حدث الآن لذا دعني أن أوضح خطوة بخطوة: كل مفتاح في الكائن (مثل الجذر root و البداية prefix) هم قواعد. كل قاعدة تحتوي على مجموعات "groups" (يوجد فقط واحدة في مثالنا هذا)، وكل مجموعة تحوي لائحة من الوصائف tags ولائحة من العبارات phrases. كل عبارة هي كتلة مستقلة من نص قد تحوي على توجيهات directives (على سبيل المثال [:prefix]) التي تشير إلى قاعدة أخرى.

هذه كيفية عمل Improv: نسأل الأداة بأن تولد جذر root القاعدة (generator.gen('root', {}) في ملف JavaScript). يقوم Improv بالتحقق عن كل المجموعات من أجل تلك القاعدة؛ يوجد بالطبع قاعدة واحدة فقط في مشروعنا هذا. توجد أولاً خطوة فلترة، وسنتحدث عنها بالتفصيل لاحقاً، تقوم باختيار المجموعات التي ستستخدمها. ثم تقوم بجمع كل العبارات وتضعها في المجموعات السابقة وتختار عبارة عشوائياً.

يتم صنع نموذج من كل عبارة؛ ينظر Improv إلى التوجيهات المحاطة [بقوسين مربعين]. التوجيهات البادئة بنقطتين منقوطتين، مثل [:prefix]، هن زبدة توليد النصوص في هذه الأداة؛ عندما يلاقي Improv أحد تلك التوجيهات، إن وردت، فإنه يولّد تلك القاعدة.

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

وبهذه البساطة، أصبح لدينا تنويعات مختلفة:


      The HMS Unstoppable is a battleship commissioned in 1898
      The HMS Invincible is a cutter commissioned in 1891
      The HMS Indefatigable is a light cruiser commissioned in 1906
      

لاحظ عبارة [#1880-1910]؛ تلك هي توجيه مميز يمكنه إنتاج رقم عشوائي بين 1880 و 1910.

التوصيف

لنقل أننا نريد تضمين كلاً من البارجات الحربية والمدنية في قواعد الإملاء. سنضيف تنويعات مدنية ذات أسماء وفئات وبدايات خاصين:

ما تغير الآن هو أنه أصبح لدينا مجموعات من العبارات لكل من السفن المدنية والحربية؛ ونحن نستخدم الآن صفة tags. أهم شيء يجب ملاحظته هنا هو أن tags هي لائحة من اللوائح list of lists. كل صفة tag هي لائحة، مثل ['type', 'military'] كل كلمة هي صفة منفردة وليس بصفتين. هذا الأمر مهم لأنه يعني أن الصفات تشكل هيكل هرمياً، وهذا الأمر له علاقة بالفلترة كما سنرى لاحقاً.

أول طريقة يتم استخدام الأوصاف فيها هي في إعادة الدمج الذي ضبطناه إلى وضع "مفعّل" سابقاً. لاحظ أنه عندما استدعينا Improv.gen() فإننا أعطيناه وسيطين؛ الأول 'root' هو اسم القاعدة التي نريد البدء منها. الثاني هو مجرد {} أي، كائن فارغ جديد. هذا هو نموذجنا، كائن يحوي بيانات عن النص الذي نترجمه. كل مرة يختار فيها Improv عبارة ويستخدمها فإنه يضيفها إلى النموذج (بافتراض أن إعادة الدمج مفعّلة). يمكننا القيام بتغيير بسيط على ملف الـ JavaScript بحيث يتم فحص النموذج بعد ذلك.

مثال للمخرج:

سنرى أن الصفة التي استخدمناها سابقاً قد أضيفت إلى كائننا الفارغ. يمكنك أن تولّد نموذجاً باكراً باستخدام وسيلة أخرى ثم تنقله إلى Improv، يمكنك أيضاً حفظ النموذج بعد توليد النص (كمتغيّر) لإعادة استخدامه، كتوليد نصوصٍ أخرى باستخدام الصفات نفسها.

الفلترة

لم نتحدث حتى الآن في هذه المقالة عن أهم ميزة في Improv: الفلترة. أتتذكر ؟filters: [Improv.filters.mismatchFilter()] ما كان يقوم به ذلك السطر هو ضبط المولد لاستخدام Improv.filters.mismatchFilter() كفلتره الوحيد. الفلتر هو مجرد دالة تساعد Improv على اختيار المجموعات التي سيتم استخدامها عند الانصياع لقاعدة. يحوي Improv.filters مجموعة من الدالات المثبتة التي تعطي فلترات جاهزة لاستخدامها في Improv، ولكن لا يمنعك شيء من كتابة فلتراتك الخاصة. يحوي كتيّب Improv على لائحة من الفلترات المثبتة وعن أمثلة لمواضع استخدامها. لكن من أجل أهداف تعليمية، سوف نلقي نظرة فقط على *mismatch filter* فلتر "عدم المطابقة".

ما يقوم به فلتر "عدم المطابقة" هو البحث عن أوصاف تناقض أوصاف النموذج، واستبعاد المجموعات التي تحوي تلك الأوصاف. إنه أنفع وأبسط فلتر موجود فهو يوقف Improv من مناقضة نفسه بفرض أنه قمنا بوضع الأوصاف في نص ملف الإملاء حتى لا يناقض نفسه.

تعريف "التناقضات" في سياقنا هو:

- النموذج والمجموعة التي تحوي على وصف بنفس العنصر الأول؛ - ولكن دون أن تكون جميع العناصر متطابقةً.

لذا، 'type', 'military' تتناقض مع 'type', 'civilian'. ولكنها تدع التكافئ التام (أي مع 'type', 'military')، كما تدع الأوصاف المختلفة كلياً (كمثال، 'propulsion', 'sail').

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

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

قراءة إضافية

لا يتعمق هذا الدرس في وظائف Improv. أكثرية الفلترات لا تقوم بطرح المجموعات كلياً؛ بل تقوم بإرجاع عدد (موجب أو سالب)، ويتم جمع تلك الأعداد المرجعة من الفلترات كلها لإعطاء كل مجموعة *درجة ملائمة*. يقوم Improv بعد ذلك باختيار ما يجب استخدامه بناءاً على درجة الملائمة. هذه العملية مفيدة لأكثر من إيقاف النص من مناقضة نفسه. يستخدم مولّد نصوص لعبة Voyageur "وصفة سرية" من وسائل الفلترة لتوليد النصوص. أحد الجوانب التي يتم تقديرها هو سعة النص الموصوف. أي محاولة استخدام أوصاف لم يتم استخدامها سابقاً لإنتاج فقرة تحوي جميع جوانب الشيء الموصف. يحاول المولّد أيضاً البحث عن التعيين. أي تثمين كتل النص المتعلقة بظروف محددة بحيث تظهر في الفرص النادرة المناسبة.

وأخيراً...

  • يتحدث دليل Improv عن جميع وظائف و API المكتبة بالتفصيل.
  • كما هو حال برنامج Tracery، فإن Improv ليس مولّداً للنصوص فحسب. يمكن توليد أي شيء ذات صيغة نصية، مثل صور من صيغة SVG أو حتى ملفات برمجية للوحوش والشخصيات في لعبة ما.