模糊逻辑。另外一个咱们常常听到的术语。但它的真正含义是什么?它是否意味着不止一件事?咱们立刻就会知道答案。html
咱们将使用模糊逻辑来帮助引导一辆自动驾驶汽车绕过障碍,若是咱们作得正确,咱们将避开沿途的障碍。咱们的自动导航车辆(AGV)将在障碍物周围导航,感知其路径上的障碍物。它将使用一个推理系统来帮助引导它前进。你或者用户将可以创造障碍或经过的方式,AGV必须避开或经过。你能够观察跟踪光束的工做,以及跟踪AGV的路径沿其路线。AGV所采起的每一步都将在用户界面上进行更新,这样您就能够看到发生了什么。数据库
在布尔逻辑中,事物或真或假,或黑或白。许多人不知道的是,还有一种被称为多值逻辑的东西,它的真实值介于1和0之间。模糊逻辑是处理部分真理的多值逻辑的概念实现。例如不少人也不知道的著名的sigmoid函数,它是一种模糊化的方法。express
维基百科(建议你们多使用这个百科,百度百科真的只能用呵呵来形容)对此有很好的描述,以下:app
维基百科:框架
"In this image, the meanings of the expressions cold, warm, and hot are represented by functions mapping a temperature scale. A point on that scale has three "truth values"-one for each of the three functions. The vertical line in the image represents a particular temperature that the three arrows (truth values) gauge. Since the red arrow points to zero,this temperature may be interpreted as "not hot". The orange arrow (pointing at 0.2) may describe it as "slightly warm" and the blue arrow (pointing at 0.8) "fairly cold"."机器学习
在这幅图中,cold(冷)、warm(暖)和hot(热)表达式的含义由映射一个温标的函数表示。这个尺度上的一个点有三个“真值”,三个函数各有一个。图像中的垂直线表示三个箭头(真值)测量的特定温度。因为红色箭头指向零,这个温度能够解释为“不热”。 橙色的箭头(指向0.2)能够描述为“轻微温暖”,蓝色的箭头(指向0.8)能够描述为“轻微温暖”或“很冷”。函数
咱们为何要展现这个?由于,这个图表和描述很是准确地描述了什么是模糊逻辑。咱们将使用AForge.NET开源机器学习框架。这是一个很好的框架,它展现了使用推理引擎完成任务是多么容易。学习
在这一章中,咱们将讨论:测试
模糊逻辑ui
避障与识别
AGV
咱们的应用程序将有两个简单的按钮,一个用于运行模糊集测试,另外一个用于运行语言变量测试。下面是示例应用程序的快速快照:
建立此示例的代码相对较小且简单。这是当咱们点击Run Fuzzy Set Test按钮时的样子。咱们将建立两个模糊集(一个用于凉爽,一个用于温暖),并为每一个模糊集添加一些成员数据值,而后绘制它们:
#region 建立两个模糊集来表示凉和暖的温度 #region 凉 TrapezoidalFunction function1 = new TrapezoidalFunction(13, 18, 23, 28); FuzzySet fsCool = new FuzzySet("凉", function1); double[,] coolValues = new double[20, 2]; for (int i = 10; i < 30; i++) { coolValues[i - 10, 0] = i; coolValues[i - 10, 1] = fsCool.GetMembership(i); } chart?.UpdateDataSeries("凉", coolValues); #endregion 凉 #region 暖 TrapezoidalFunction function2 = new TrapezoidalFunction(23, 28, 33, 38); FuzzySet fsWarm = new FuzzySet("暖", function2); double[,] warmValues = new double[20, 2]; for (int i = 20; i < 40; i++) { warmValues[i - 20, 0] = i; warmValues[i - 20, 1] = fsWarm.GetMembership(i); } chart?.UpdateDataSeries("暖", warmValues); #endregion 暖 #endregion 建立两个模糊集来表示凉和暖的温度
运行语言变量测试的代码以下。一样,咱们建立模糊集,但此次咱们建立4个而不是2个。与咱们的第一个测试同样,咱们首先添加成员数据,而后绘图:
LinguisticVariable lvTemperature = new LinguisticVariable("温度", 0, 80); TrapezoidalFunction function1 = new TrapezoidalFunction(10, 15, TrapezoidalFunction.EdgeType.Right); FuzzySet fsCold = new FuzzySet("冷", function1); lvTemperature.AddLabel(fsCold); TrapezoidalFunction function2 = new TrapezoidalFunction(10, 15, 20, 25); FuzzySet fsCool = new FuzzySet("凉", function2); lvTemperature.AddLabel(fsCool); TrapezoidalFunction function3 = new TrapezoidalFunction(20, 25, 30, 35); FuzzySet fsWarm = new FuzzySet("暖", function3); lvTemperature.AddLabel(fsWarm); TrapezoidalFunction function4 = new TrapezoidalFunction(30, 35, TrapezoidalFunction.EdgeType.Left); FuzzySet fsHot = new FuzzySet("热", function4); lvTemperature.AddLabel(fsHot); double[][,] chartValues = new double[4][,]; for (int i = 0; i < 4; i++) chartValues[i] = new double[160, 2];
最后咱们画出这些值:
#region 语言变量的形状——它的标签从开始到结束的形状 int j = 0; for (float x = 0; x < 80; x += 0.5f, j++) { double y1 = lvTemperature.GetLabelMembership("冷", x); double y2 = lvTemperature.GetLabelMembership("凉", x); double y3 = lvTemperature.GetLabelMembership("暖", x); double y4 = lvTemperature.GetLabelMembership("热", x); chartValues[0][j, 0] = x; chartValues[0][j, 1] = y1; chartValues[1][j, 0] = x; chartValues[1][j, 1] = y2; chartValues[2][j, 0] = x; chartValues[2][j, 1] = y3; chartValues[3][j, 0] = x; chartValues[3][j, 1] = y4; } chart.UpdateDataSeries("冷", chartValues[0]); chart.UpdateDataSeries("凉", chartValues[1]); chart.UpdateDataSeries("暖", chartValues[2]); chart.UpdateDataSeries("热", chartValues[3]); #endregion 语言变量的形状——它的标签从开始到结束的形状
语言变量的形状:
正如所看到的,咱们可以很容易地展现出维基百科定义所展现的视觉定义。
在咱们继续以前,先看一下咱们的应用程序将是什么样子的,而后对推理引擎进行简要的解释:
尽管AForge.NET使得咱们很容易和透明的建立一个InferenceSystem(推理系统)对象。
让我先来解释下什么是模糊推理系统。模糊推理系统是一种可以进行模糊计算的模型。这是使用数据库、语言变量和规则库完成的,全部这些均可以存储在内存中。
模糊推理系统的典型操做以下:
对于咱们来说,大部分工做将在初始化咱们的模糊逻辑系统时进行。让咱们将其分解为前面概述的各个步骤。
首先,咱们须要准备语言标签(模糊集)组成的距离。他们分别是Near(近)、Medium(中)和Far(远)。
#region 构成这些距离的语言标签(模糊集) FuzzySet fsNear = new FuzzySet("Near", new TrapezoidalFunction(15, 50, TrapezoidalFunction.EdgeType.Right)); FuzzySet fsMedium = new FuzzySet("Medium", new TrapezoidalFunction(15, 50, 60, 100)); FuzzySet fsFar = new FuzzySet("Far", new TrapezoidalFunction(60, 100, TrapezoidalFunction.EdgeType.Left)); #endregion 构成这些距离的语言标签(模糊集)
接下来,咱们初始化所需的语言变量。第一个是lvRight,它将是右侧测量距离的变量:
#region 右侧测量距离(输入) LinguisticVariable lvRight = new LinguisticVariable("RightDistance", 0, 120); lvRight.AddLabel(fsNear); lvRight.AddLabel(fsMedium); lvRight.AddLabel(fsFar); #endregion 右侧测量距离(输入)
如今,咱们对左侧测量距离的变量作一样的操做:
#region 左侧测量距离(输入) LinguisticVariable lvLeft = new LinguisticVariable("LeftDistance", 0, 120); lvLeft.AddLabel(fsNear); lvLeft.AddLabel(fsMedium); lvLeft.AddLabel(fsFar); #endregion 左侧测量距离(输入)
最后一个语言变量是前方测量距离:
#region 前方测量距离(输入) LinguisticVariable lvFront = new LinguisticVariable("FrontalDistance", 0, 120); lvFront.AddLabel(fsNear); lvFront.AddLabel(fsMedium); lvFront.AddLabel(fsFar); #endregion 前方测量距离(输入)
如今咱们关注组成这个角度的语言标签(模糊集)。咱们须要完成这一步,这样才能建立最终的语言变量:
#region 组成角度的语言标签(模糊集) FuzzySet fsVN = new FuzzySet("VeryNegative", new TrapezoidalFunction(-40, -35, TrapezoidalFunction.EdgeType.Right)); FuzzySet fsN = new FuzzySet("Negative", new TrapezoidalFunction(-40, -35, -25, -20)); FuzzySet fsLN = new FuzzySet("LittleNegative", new TrapezoidalFunction(-25, -20, -10, -5)); FuzzySet fsZero = new FuzzySet("Zero", new TrapezoidalFunction(-10, 5, 5, 10)); FuzzySet fsLP = new FuzzySet("LittlePositive", new TrapezoidalFunction(5, 10, 20, 25)); FuzzySet fsP = new FuzzySet("Positive", new TrapezoidalFunction(20, 25, 35, 40)); FuzzySet fsVP = new FuzzySet("VeryPositive", new TrapezoidalFunction(35, 40, TrapezoidalFunction.EdgeType.Left)); #endregion 组成角度的语言标签(模糊集)
如今咱们能够建立角度的最终语言变量:
#region 角 LinguisticVariable lvAngle = new LinguisticVariable("Angle", -50, 50); lvAngle.AddLabel(fsVN); lvAngle.AddLabel(fsN); lvAngle.AddLabel(fsLN); lvAngle.AddLabel(fsZero); lvAngle.AddLabel(fsLP); lvAngle.AddLabel(fsP); lvAngle.AddLabel(fsVP); #endregion 角
如今咱们能够继续建立咱们的模糊数据库。对于咱们的应用程序,这是一个语言变量的内存字典,若是您愿意的话能够将其实现为SQL、NoSQL或任何其余类型的具体数据库。
#region 数据库 Database fuzzyDB = new Database(); fuzzyDB.AddVariable(lvFront); fuzzyDB.AddVariable(lvLeft); fuzzyDB.AddVariable(lvRight); fuzzyDB.AddVariable(lvAngle); #endregion 数据库
接下来,咱们将建立主推理引擎。下一行代码中最有趣的是CentroidDifuzzifier。在推理过程的最后,咱们须要一个数值来控制过程的其余部分。为了获得这个数字,咱们采用了一种去模糊化的方法。
咱们的模糊推理系统的输出是一组点火强度大于零的规则。这种点火强度对随后的模糊规则集施加了约束。当咱们把这些模糊集合放在一块儿时,它们会造成一个形状,这就是语言输出的意义。重心法将计算咱们的形状面积的中心,以得到输出的数值表示。它使用近似数,因此会选择几个区间进行计算。随着时间间隔的增长,输出的精度也会增长:
// 建立推理系统 IS = new InferenceSystem(fuzzyDB, new CentroidDefuzzifier(1000));
接下来,咱们能够开始向咱们的推理系统添加规则:
// 直走 IS.NewRule("规则 1", "IF FrontalDistance IS Far THEN Angle IS Zero"); // 直走(若是能够走到任何地方) IS.NewRule("规则 2", "IF FrontalDistance IS Far AND RightDistance IS Far AND LeftDistance IS Far THEN Angle IS Zero"); // 右侧有墙 IS.NewRule("规则 3", "IF RightDistance IS Near AND LeftDistance IS Not Near THEN Angle IS LittleNegative"); // 左侧有墙 IS.NewRule("规则 4", "IF RightDistance IS Not Near AND LeftDistance IS Near THEN Angle IS LittlePositive"); // 前方有墙 - 房间在右侧 IS.NewRule("规则 5", "IF RightDistance IS Far AND FrontalDistance IS Near THEN Angle IS Positive"); // 前方有墙 - 房间在左侧 IS.NewRule("规则 6", "IF LeftDistance IS Far AND FrontalDistance IS Near THEN Angle IS Negative"); // 前方有墙 -两边都是房间 - 向右走 IS.NewRule("规则 7", "IF RightDistance IS Far AND LeftDistance IS Far AND FrontalDistance IS Near THEN Angle IS Positive");
通过全部这些工做,咱们的推理系统就已经准备好了!
if (FirstInference) GetMeasures(); try { DoInference(); MoveAGV(); GetMeasures(); } catch (Exception ex) { Debug.WriteLine(ex); }
应用程序的主代码循环以下所示。咱们将详细描述每一个功能:
让咱们快速看一下GetMeasures函数。获取当前地图以及AGV的位置后,调用HandleAGVOnWall函数,用于处理AGV碰到墙壁没法移动的状况。在这以后,DrawAGV在地图中绘制AGV。最后,RefreshTerrain 刷新地图:
/// <summary> /// 获得传感器的测量结果 /// </summary> private void GetMeasures() { #region 得到自主移动小车的位置 pbTerrain.Image = CopyImage(OriginalMap); Bitmap b = (Bitmap)pbTerrain.Image; Point pPos = new Point(pbRobot.Left - pbTerrain.Left + 5, pbRobot.Top - pbTerrain.Top + 5); #endregion 得到自主移动小车的位置 HandleAGVOnWall(b, pPos); DrawAGV(pPos, b); RefreshTerrain(); }
DrawAGV向左和向右遇到任何障碍时,若是选中Show beam复选框,就会看到前面、左边和右边的避束检测器显示:
/// <summary> /// 绘制AGV /// </summary> /// <param name="pPos">坐标</param> /// <param name="b">位图</param> private void DrawAGV(Point pPos, Bitmap b) { Point pFrontObstacle = GetObstacle(pPos, b, -1, 0); Point pLeftObstacle = GetObstacle(pPos, b, 1, 90); Point pRightObstacle = GetObstacle(pPos, b, 1, -90); #region 显示线束 Graphics g = Graphics.FromImage(b); if (cbLasers.Checked) { g.DrawLine(new Pen(Color.Red, 1), pFrontObstacle, pPos); g.DrawLine(new Pen(Color.Red, 1), pLeftObstacle, pPos); g.DrawLine(new Pen(Color.Red, 1), pRightObstacle, pPos); } #endregion 显示线束 #region 绘制AGV if (btnRun.Text != RunLabel) { g.FillEllipse(new SolidBrush(Color.Blue), pPos.X - 5, pPos.Y - 5, 10, 10); } g.DrawImage(b, 0, 0); g.Dispose(); #endregion 绘制AGV #region 更新显示的距离 txtFront.Text = GetDistance(pPos, pFrontObstacle).ToString(); txtLeft.Text = GetDistance(pPos, pLeftObstacle).ToString(); txtRight.Text = GetDistance(pPos, pRightObstacle).ToString(); #endregion 更新显示的距离 }
DoInference函数运行咱们的模糊推理系统的一个历元(实例、生成等等)。最终,它负责肯定AGV的下一个角度。
/// <summary> /// 运行模糊推理系统的一个纪元 /// </summary> private void DoInference() { // 输入设置 IS?.SetInput("RightDistance", Convert.ToSingle(txtRight.Text)); IS?.SetInput("LeftDistance", Convert.ToSingle(txtLeft.Text)); IS?.SetInput("FrontalDistance", Convert.ToSingle(txtFront.Text)); // 输出设置 try { double NewAngle = IS.Evaluate("Angle"); txtAngle.Text = NewAngle.ToString("##0.#0"); Angle += NewAngle; } catch (Exception) { } }
MoveAGV函数负责将AGV移动一步。若是您检查了跟踪路径的话,会发现这个函数中大约50%的代码时用于绘制AGV的历史轨迹的。
/// <summary> /// 移动AGV /// </summary> private void MoveAGV() { double rad = ((Angle + 90) * Math.PI) / 180; int Offset = 0; int Inc = -4; Offset += Inc; int IncX = Convert.ToInt32(Offset * Math.Cos(rad)); int IncY = Convert.ToInt32(Offset * Math.Sin(rad)); if (cbTrajeto.Checked) { Graphics g = Graphics.FromImage(OriginalMap); Point p1 = new Point(pbRobot.Left - pbTerrain.Left + pbRobot.Width / 2, pbRobot.Top - pbTerrain.Top + pbRobot.Height / 2); Point p2 = new Point(p1.X + IncX, p1.Y + IncY); g.DrawLine(new Pen(new SolidBrush(Color.Green)), p1, p2); g.DrawImage(OriginalMap, 0, 0); g.Dispose(); } pbRobot.Top = pbRobot.Top + IncY; pbRobot.Left = pbRobot.Left + IncX; }
主要应用与显示光束的选择:
随着应用程序的运行,AGV成功导航障碍物,显示路径和光束。角度是AGV当前所面对的角度,传感器读数与前、左、右波束传感器有关:
咱们的AGV成功地完成了穿越障碍,并继续运行:
轨迹路径和显示光束可单独选择:
在这一章中,咱们学习了各类类型的模糊逻辑实现,并看到了使用AForge.NET将这种逻辑添加到咱们的应用程序中是多么的容易。在咱们的下一章中,咱们将开始深刻研究自组织地图,将咱们的机器学习技能提高到一个新的层次。若是你还记得小学时上的美术课,这一章必定会给你带来回忆。