基於C#的機器學習--模糊邏輯-穿越障礙

 

模糊邏輯-穿越障礙

模糊邏輯。另外一個咱們常常聽到的術語。但它的真正含義是什麼?它是否意味着不止一件事?咱們立刻就會知道答案。html

咱們將使用模糊邏輯來幫助引導一輛自動駕駛汽車繞過障礙,若是咱們作得正確,咱們將避開沿途的障礙。咱們的自動導航車輛(AGV)將在障礙物周圍導航,感知其路徑上的障礙物。它將使用一個推理系統來幫助引導它前進。你或者用戶將可以創造障礙或經過的方式,AGV必須避開或經過。你能夠觀察跟蹤光束的工做,以及跟蹤AGV的路徑沿其路線。AGV所採起的每一步都將在用戶界面上進行更新,這樣您就能夠看到發生了什麼。數據庫

在布爾邏輯中,事物或真或假,或黑或白。許多人不知道的是,還有一種被稱爲多值邏輯的東西,它的真實值介於10之間。模糊邏輯是處理部分真理的多值邏輯的概念實現。例如不少人也不知道的著名的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開源機器學習框架。這是一個很好的框架,它展現了使用推理引擎完成任務是多麼容易。學習

       在這一章中,咱們將討論:測試

  1. 模糊邏輯ui

  2. 避障與識別

  3. 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推理系統)對象。

       讓我先來解釋下什麼是模糊推理系統。模糊推理系統是一種可以進行模糊計算的模型。這是使用數據庫、語言變量和規則庫完成的,全部這些均可以存儲在內存中。

       模糊推理系統的典型操做以下:

  1. 獲取數字輸入
  2. 結合激活規則的結果獲得一個模糊輸出
  3. 驗證輸入激活了來自規則庫的哪些規則
  4. 利用帶有語言變量的數據庫獲取每一個數字輸入的語言意義

對於咱們來說,大部分工做將在初始化咱們的模糊邏輯系統時進行。讓咱們將其分解爲前面概述的各個步驟。

首先,咱們須要準備語言標籤(模糊集)組成的距離。他們分別是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 角 

如今咱們能夠繼續建立咱們的模糊數據庫。對於咱們的應用程序,這是一個語言變量的內存字典,若是您願意的話能夠將其實現爲SQLNoSQL或任何其餘類型的具體數據庫。

        #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將這種邏輯添加到咱們的應用程序中是多麼的容易。在咱們的下一章中,咱們將開始深刻研究自組織地圖,將咱們的機器學習技能提高到一個新的層次。若是你還記得小學時上的美術課,這一章必定會給你帶來回憶。

相關文章
相關標籤/搜索