تعلم الذكاء الاصطناعي لتلعب لعبة

يوم جيد عزيزي القارئ!

في هذه المقالة سنقوم بتطوير شبكة عصبية يمكنها تمرير لعبة تم إنشاؤها خصيصًا لها بمستوى جيد.



ملاحظة: لا تشرح هذه المقالة مصطلح " الشبكة العصبية " وكل ما يتعلق بها ، كما أنها لا توفر معلومات أساسية حول التدريب على الشبكة باستخدام طريقة التتبع . نوصي بأن تتعرف على هذه المفاهيم لفترة وجيزة قبل قراءة هذه المقالة.

ابدأ: وصف قواعد اللعبة


الهدف هو التقاط أكبر عدد ممكن من الدوائر ذات الحافة الخضراء ، وتجنب الدوائر ذات اللون الأحمر.

الشروط:

  • يطير في الحقل ضعف عدد الدوائر الحمراء مثل الدوائر الخضراء ؛
  • تبقى الدوائر الخضراء داخل الحقل ، وتطير الدوائر الحمراء خارج الحقل ويتم التخلص منها ؛
  • يمكن للدوائر الخضراء والحمراء أن تتصادم وتتصدى من نوعها.
  • لاعب - كرة صفراء على الشاشة ، يمكن أن تتحرك داخل الملعب.

بعد لمس الكرة يختفي ، ويمنح اللاعب النقاط المقابلة.

التالي: تصميم الذكاء الاصطناعي


المستقبلات


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

سيكون هناك 68: أول 32 - الرد على القنابل (الدوائر الحمراء) ، و 32 التالية - الرد على التفاح (الدوائر الخضراء) ، والأخيرة 4 - تلقي البيانات حول موقع حدود الملعب بالنسبة للاعب. دعونا نطلق على هذه الماسحات الضوئية الـ 68 الخلايا العصبية المدخلة للشبكة العصبية المستقبلية (طبقة المستقبلات).

يبلغ طول أول 64 ماسحة ضوئية 1000 بكسل ، وتقابل الأربعة المتبقية تقسيم المجال إلى النصف وفقًا لوجه التماثل المقابل
الصورة نطاق AI (1/4)
الصورة

في الشكل: الخلايا العصبية المدخلة تتلقى قيمًا من الماسحات الضوئية
يتم تطبيع القيم الموجودة على الماسحات الضوئية ، أي يتم تقليله إلى النطاق [0 ، 1] ، وكلما اقترب الجسم من الماسح الضوئي من المشغل ، زادت القيمة المرسلة إلى الماسح الضوئي.

خوارزمية لاستقبال البيانات بواسطة الماسحات الضوئية وتنفيذها على JS
لذا ، فإن الماسح الضوئي مباشر. لدينا إحداثيات إحدى نقاطها (إحداثيات اللاعب) والزاوية المتعلقة بمحور OX ، يمكننا الحصول على النقطة الثانية باستخدام الدوال المثلثية sin و cos.

من هنا نحصل على متجه التوجيه للخط ، مما يعني أنه يمكننا إنشاء الشكل الأساسي لمعادلة هذا الخط.

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

إذا جلبنا هذا الاستبدال إلى صيغة عامة ، نحصل على معادلة معلمية فيما يتعلق a و b و c ، حيث تكون هذه المتغيرات هي معاملات المعادلة التربيعية ، بدءًا من المربع ، على التوالي.

ندعو القارئ إلى التعرف على هذه الخوارزمية بمزيد من التفصيل باستخدام أبسط تعريفات الجبر الخطي
يوجد أدناه رمز الماسحات الضوئية

Ball.scaners: { // length: 1000, i: [], //   get_data: function(){ //Ball.scaners.get_data /     var angl = 0; var x0 = Ball.x, var y0 = Ball.y; var l = Ball.scaners.length; for(let k = 0; k < 32; k++){ x1 = l*Math.cos(angl), y1 = l*Math.sin(angl); Ball.scaners.i[k] = 0; for(i = 0; i < bombs.length; i++){ if(((k >= 0) && (k <= 8) && (bombs[i].x < x0) && (bombs[i].y < y0)) || ((k >= 8) && (k <= 16) && (bombs[i].x > x0) && (bombs[i].y < y0)) || ((k >= 16) && (k <= 24) && (bombs[i].x > x0) && (bombs[i].y > y0)) || ((k >= 24) && (k <= 32) && (bombs[i].x < x0) && (bombs[i].y > y0))){ //    var x2 = bombs[i].x, y2 = bombs[i].y; var p = true; //    var pt = true; //     var t1, t2; var a = x1*x1 + y1*y1, b = 2*(x1*(x0 - x2) + y1*(y0 - y2)), c = (x0 - x2)*(x0 - x2) + (y0 - y2)*(y0 - y2) - bombs[i].r*bombs[i].r; //------------------------------    if((a == 0) && (b != 0)){ t = -c/b; pt = false; } if((a == 0) && (b == 0)){ p = false; } if((a != 0) && (b != 0) && (c == 0)){ t1 = 0; t2 = b/a; } if((a != 0) && (b == 0) && (c == 0)){ t1 = 0; pt = false; } if((a != 0) && (b == 0) && (c != 0)){ t1 = Math.sqrt(c/a); t2 = -Math.sqrt(c/a); } if((a != 0) && (b != 0) && (c != 0)){ var d = b*b - 4*a*c; if(d > 0){ t1 = (-b + Math.sqrt(d))/(2*a); t2 = (-b - Math.sqrt(d))/(2*a); } if(d == 0){ t1 = -b/(2*a); } if(d < 0){ p = false; } } //----------------------------------- if(p == true){ if(pt == true){ let x = t1*x1 + x0; let y = t1*y1 + y0; let l1 = Math.pow((x - Ball.x), 2)+Math.pow((y - Ball.y), 2); x = t2*x1 + x0; y = t2*y1 + y0; let l2 = Math.pow((x - Ball.x), 2)+Math.pow((y - Ball.y), 2); if(l1 <= l2){ Ball.scaners.i[k] += 1 - l1/(l*l); }else{ Ball.scaners.i[k] += 1 - l2/(l*l); } }else{ let x = t1*x1 + x0; let y = t1*y1 + y0; Ball.scaners.i[k] += 1 - (Math.pow((x - Ball.x), 2)+Math.pow((y - Ball.y), 2))/(l*l); } }else{ Ball.scaners.i[k] += 0; } }else{ continue; } } angl += Math.PI/16; } //!---------------  for(k = 32; k < 64; k++){ x1 = l*Math.cos(angl), y1 = l*Math.sin(angl); Ball.scaners.i[k] = 0; for(i = 0; i < apples.length; i++){ if(((k >= 32) && (k <= 40) && (apples[i].x < x0) && (apples[i].y < y0)) || ((k >= 40) && (k <= 48) && (apples[i].x > x0) && (apples[i].y < y0)) || ((k >= 48) && (k <= 56) && (apples[i].x > x0) && (apples[i].y > y0)) || ((k >= 56) && (k <= 64) && (apples[i].x < x0) && (apples[i].y > y0))){ var x2 = apples[i].x, var y2 = apples[i].y; var p = true; //    var pt = true; //     var t1, t2; var a = x1*x1 + y1*y1, b = 2*(x1*(x0 - x2) + y1*(y0 - y2)), c = (x0 - x2)*(x0 - x2) + (y0 - y2)*(y0 - y2) - apples[i].r*apples[i].r; //------------------------------    if((a == 0) && (b != 0)){ t = -c/b; pt = false; } if((a == 0) && (b == 0)){ p = false; } if((a != 0) && (b != 0) && (c == 0)){ t1 = 0; t2 = b/a; } if((a != 0) && (b == 0) && (c == 0)){ t1 = 0; pt = false; } if((a != 0) && (b == 0) && (c != 0)){ t1 = Math.sqrt(c/a); t2 = -Math.sqrt(c/a); } if((a != 0) && (b != 0) && (c != 0)){ var d = b*b - 4*a*c; if(d > 0){ t1 = (-b + Math.sqrt(d))/(2*a); t2 = (-b - Math.sqrt(d))/(2*a); } if(d == 0){ t1 = -b/(2*a); } if(d < 0){ p = false; } } //----------------------------------- if(p == true){ if(pt == true){ let x = t1*x1 + x0; let y = t1*y1 + y0; let l1 = Math.pow((x - Ball.x), 2)+Math.pow((y - Ball.y), 2); x = t2*x1 + x0; y = t2*y1 + y0; let l2 = Math.pow((x - Ball.x), 2)+Math.pow((y - Ball.y), 2); if(l1 <= l2){ Ball.scaners.i[k] += 1 - l1/(l*l); }else{ Ball.scaners.i[k] += 1 - l2/(l*l); } }else{ let x = t1*x1 + x0; let y = t1*y1 + y0; Ball.scaners.i[k] += 1 - (Math.pow((x - Ball.x), 2)+Math.pow((y - Ball.y), 2))/(l*l); } }else{ Ball.scaners.i[k] += 0; } }else{ continue; } } angl += Math.PI/16; } Ball.scaners.i[64] = (1000 - Ball.x) / 1000; //  Ball.scaners.i[65] = Ball.x / 1000; //  Ball.scaners.i[66] = (500 - Ball.y) / 500; //  Ball.scaners.i[67] = Ball.y / 500; //  } } 

تصميم الشبكات العصبية


لذا ، كان من المعتاد استخدام خوارزمية التتبع (المسارات المرجعية) لتدريب NS.
ملاحظة: من أجل تبسيط عملية التعلم ، يتم حذف بناء المصفوفات.

متابعة:

  1. عند إخراج الشبكة ، نقدم 8 خلايا عصبية مسؤولة عن التوجيهات: اليسار الأول ، والثاني هو اليسار + الأعلى ، والثالث هو الأعلى ، والرابع هو الأعلى + اليمين ، والخامس هو اليمين وما إلى ذلك ؛
  2. دع القيمة الإيجابية للخلايا العصبية تعني أنه يجب عليك التحرك في الاتجاه المرتبط بها ، والسلبية - العكس ؛
  3. من المهم بالنسبة لنا أن نجمع بطريقة ما القيم من الماسحات الضوئية الموجودة في نفس الزاوية بالنسبة للحافة السفلية للحقل. نظرًا لأن الناتج السلبي سينتج الذكاء الاصطناعي من هذا الاتجاه ، فإننا نقدم طبقة إضافية (مخفية بين المدخلات والمخرجات) ، والتي ستضيف قيم الخلايا العصبية التي تعكس الماسحات الضوئية المقابلة في أزواج ، وسنأخذ القيم من الخلايا العصبية الحمراء مع "-" ؛
  4. من الواضح أن الحركة إلى اليسار تتأثر بشكل رئيسي بالمعلومات الموجودة على يسار اللاعب (ليس فقط ، ولكن سيتم أخذ ذلك في الاعتبار لاحقًا (*)) - وهذا يعني أننا نربط 8 خلايا عصبية من الطبقة المخفية الموجودة على اليسار مع العصبون المسؤول عن الانتقال إلى اليسار. نحدد الروابط على النحو التالي: يتم تعيين وزن 1 للخلايا العصبية المقابلة للماسح الضوئي المتوازي مع حدود المجال ، ثم يتم تعيين وزن للخلايا العصبية المجاورة 0.95 ، والخلايا المجاورة من خلال وزن 0.9 ، وأخيرًا ، آخر واحد في هذا القطاع هو الوزن 0.8 ؛
  5. نأخذ في الاعتبار أيضًا الخلايا العصبية الحدودية: نوجه الاتصالات من تلك الخلايا العصبية الحدودية إلى المخرجات التي يمكن أن تؤثر على حركة الحدود.
  6. يفعل الشيء نفسه مع الخلايا العصبية السبعة المتبقية من طبقة الإخراج.

بعد إكمال الخوارزمية أعلاه ، نحصل على الشبكة العصبية الموضحة في الشكل أدناه

الصورة

* لكن هذا ليس كل شيء. من المهم تفسير النتيجة بشكل صحيح ، وهي (Y عبارة عن مجموعة من الخلايا العصبية الناتجة):
  Ball.vx = -(Y[0] - Y[4]) + (-(Y[1] - Y[5]) + (Y[3] - Y[6]))*0.5; Ball.vy = -(Y[2] - Y[6]) + (-(Y[3] - Y[7]) + (Y[5] - Y[1]))*0.5; 


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

الكود متاح (تنفيذ اللعبة و AI) على الرابط: Link to the github Ivan753 / Learning

هذا كل شيء. شكرا لكم على اهتمامكم!

Source: https://habr.com/ru/post/ar418895/


All Articles