学习人工智能玩游戏

美好的一天,亲爱的读者!

在本文中,我们将开发一个神经网络,可以很好地传递专门为其创建的游戏。



注意:本文不会解释“ 神经网络 ”一词及其相关的所有内容,也不会提供有关使用跟踪方法进行网络训练的基本信息。 我们建议您在阅读本文之前简要熟悉这些概念。

开始:游戏规则说明


目标是捕捉尽可能多的绿色边缘的圆圈,避免出现红色的圆圈。

条款:

  • 飞越绿色圆圈的红色圆圈是绿色圆圈的两倍。
  • 绿圈保留在视野内,红色圆圈飞出视野并被消除;
  • 绿色和红色圆圈可以相互碰撞并排斥。
  • 播放器-屏幕上的黄球可以在场地内移动。

碰到球后消失,并向球员授予相应的积分。

下一步:设计AI


受体


为了使AI能够决定将播放器移动到何处,首先需要获取环境数据。 为此,创建特殊的直线扫描仪 。 第一扫描仪相对于视野的下边界成180度角。

将有68个:前32个-响应炸弹(红色圆圈),接下来的32个-响应苹果(绿色圆圈),最后4个-接收有关玩家相对于场边界位置的数据。 让我们将这68个扫描器称为未来神经网络(受体层)的输入神经元

前64个扫描仪的长度为1000像素,其余4个对应于根据对应的对称面将场分成两半
图片 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. 对于我们而言,以某种方式组合相对于视野下边缘成相同角度的扫描仪的值非常重要。 由于AI的输出为负值,因此AI将沿该方向排斥,因此我们引入了一个附加层(隐藏在输入和输出之间),它将添加成对反映相应扫描器的神经元的值,并且我们将红色神经元的值取为“-”;
  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; 


因此,我们在文章顶部的gif上创建并自动训练了神经网络,该神经网络是玩家AI的基础

链接中提供了代码(游戏和AI的实现): 链接至github Ivan753 / Learning

仅此而已。 感谢您的关注!

Source: https://habr.com/ru/post/zh-CN418895/


All Articles