<blockquote id="g5mpq"><rt id="g5mpq"></rt></blockquote>

    1. <pre id="g5mpq"></pre>
      <i id="g5mpq"><legend id="g5mpq"></legend></i>
      浪漫女家教主演:黛比地区:台湾 ,日本jiZz,爸爸的种子在线观看,特别的酒店2免费,哇嘎在线,荒野渔夫高清免费观看,新有菜在线免费观看,哇嘎美国
      網易首頁 > 網易號 > 正文 申請入駐

      游戲AI行為決策——MLP(多層感知機/人工神經網絡)

      0
      分享至


      【USparkle專欄】如果你深懷絕技,愛“搞點研究”,樂于分享也博采眾長,我們期待你的加入,讓智慧的火花碰撞交織,讓知識的傳遞生生不息!

      這是侑虎科技第1902篇文章,感謝作者狐王駕虎供稿。歡迎轉發分享,未經作者授權請勿轉載。如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群:793972859)

      作者主頁:

      https://home.cnblogs.com/u/OwlCat

      你一定聽說過神經網絡的大名,你有想過將它用于游戲AI的行為決策上嗎?其實在(2010年發布的)《最高指揮官2》中就有應用了,今天請允許我班門弄斧一番,與大家一同用C# 實現最經典的神經網絡 —— 多層感知機(Multilayer Perceptron,簡稱MLP)。


      一、前言

      神經網絡或者深度學習,總給人一種「量子力學」的感覺,總感覺它神秘無比,又無所不能。我未學習神經網絡之前,總以為它是某種能夠修改自身代碼的代碼,否則怎么能做到從「不會」變成「會」的呢?但在親自學習后才會明白,它并沒有做到這種地步,但依舊十分神奇。多層感知機是最基礎的神經網絡,很多其它類別的神經網絡都是在這之上的變形。可以說,學會它是邁入深度學習的第一步。

      多層感知機雖說經典,但并不過時。提到神經網絡,大多數人腦海里想到的大概也就是類似這樣的圖片:


      這就是一張典型的多層感知機結構圖,看著好像很復雜,但實現它所需要用到的數學原理和編程知識都不難,早年間,研究神經網絡的學者們還用C語言實現呢!

      二、什么是多層感知機

      現在進入正題,我們先來簡單講講MLP的原理(如果你對此十分熟悉,只是對代碼實現感興趣,那可以跳過這部分)。

      既然叫「多層感知機」,那有單個的感知機嗎?那是自然,單個感知機的結構十分簡單:


      它其實就是個算式(為方便理解,我將其分成兩部分):


      將傳入感知機的多個輸入x,與對應的權重w相乘(輸入的數量與權重的數量是一樣的,且數量是任意的,本例中用了3個),再加上偏置b就可以得出一個計算值sum。再將這個計算值傳入一個函數f(x)就可得到感知機的最終輸出out。

      相信你肯定能理解,只是可能對f(x)函數有些好奇,它具體內容是什么呢?這個函數也被稱為「激活函數」,為什么叫這個名字?這就得提感知機的另一個名字 —— 人工神經元。其實感知機正是受神經元結構啟發而被提出來的:


      神經元會通過樹突接受輸入信號并匯總,而神經元對于各個輸入刺激的響應強度并不相同,所以我們給各個輸入設置了相應權重來模擬這個現象。之后,將處理的信息通過軸突傳給末梢(終端)。但實際上,只有匯總的信號強度大于一定程度時神經元才會向末梢傳遞信號,而模擬這個現象的就是「激活函數」。

      那偏置b又是模擬什么的?其實它是從數學角度考慮的、方便調整輸入加權和的變量值而已。

      既然感知機被稱為「人工神經元」,那多層感知機豈不就是「人工神經網絡」?一點沒錯,我們現在所說的「神經網絡」,基本都是指人工神經網絡,而不是真正的、生物的神經網絡。而「神經網絡」起初就是指多層感知機,只不過現在種類多了,定義也變寬泛了。

      結合我們對單個感知機的認識,再看看多層感知機:


      但這里的單個感知機(后面用「神經元」來代稱)怎么輸出了多個值(看標藍色的那部分)?這種結構圖可能會誤導某些人,我做個解釋,這里的每一條線并不是輸出,可以看到它是有箭頭的,每條線表示它所指向的那個神經元的一個權重。加上箭頭是為了表示數據傳遞的方向。

      不難看出,它就是將多個感知機以層為單位進行了組合,每層都有任意數量(每層的數量可以不同)的感知機,并將一層感知機的輸出作為下一層的輸入,依次套娃下去。像圖中,下一層神經元的權重數量 = 上一層的神經元數量,稱為全連接,是神經網絡中常見的連接方式,本文也只考慮這種連接方式。

      這里的「輸入層」其實就是輸入的數據(是的,這一層不是神經元),類似之前的x0、x1、x2;「輸出層」就是用于輸出的神經元所組成的層,有了多個感知機,我們也可以得到多個輸出;夾在「輸入層」與「輸出層」之間的就叫「隱藏層」,因為在實際使用神經網絡時,就只是輸入一組數值作為「輸入層」,再看看「輸出層」得到的結果,并不關心中間的運算。

      我們常說的「深度學習」里的「深度」指的就是神經網絡中「隱藏層」的層數(只不過現在這個詞有點被炒作了),當一個神經網絡的隱藏層超過3層時,它就是「深度神經網絡」。

      通過改變神經網絡的結構或者調節神經網絡的權重和偏置,我們可以用神經網絡近似任何的函數、甚至是一些摸不著頭腦的規律。

      比如影響小明今天玩不玩網游的因素有:今日作業量、心情、本月剩余流量、今天是星期幾,但我們并不知道這些因素與小明玩不玩的具體數學關系,只能大概地推斷:今天小明作業多,不會玩游戲;又或者今天是星期六,雖然作業還有很多,但他還是會玩游戲……可一旦知道具體數學關系,我們就可以通過計算準確預測小明是否會玩游戲,就像我們知道了牛頓力學公式,就可以根據物體的質量和被射出的力來計算它的運動軌跡一樣。


      所以我們所關心的、實際所使用的都是這種已經設置好正確權重和偏置的神經網絡,像在與GPT聊天時怕「污染數據庫」這類事就不用操心了。

      要如何為神經網絡的各個神經元的各個權重設置正確的值,使它能夠輸出我們預期的結果呢?手動調肯定不現實,所以我們會運用一些數學知識讓程序自行調整權重,這個過程就是「訓練/學習」

      我們會給出一些輸入以及該輸入所對應的正確輸出,比如我們可以記錄小明上個學期玩網游時的各因素值以及不玩時的各因素值,這些作為「訓練集」。然后設計一個「損失函數」評判當前神經網絡的輸出與正確輸出之間的差距。而程序就是不斷地調節各個權重,使差距越來越小,這種調節的根據是「導數」,但在這里我就不展開了。總之,如果訓練得當,神經網絡的損失就會越來越小,直到停在一個值附近,這就是「收斂」


      篇幅所限,我刻意沒有講相關的數學原理,如果你對此感興趣,又或者對MLP的運作仍有困惑,可以看看以下兩個視頻。如果準備好了,下面就進入代碼實現環節吧。

      視頻1:


      https://www.bilibili.com/video/BV1bx411M7Zx/?spm_id_from=333.999.0.0&vd_source=c9a1131d04faacd4a397411965ea21f4

      視頻2:


      https://www.bilibili.com/video/BV1o64y1i7yw/?spm_id_from=333.788&vd_source=c9a1131d04faacd4a397411965ea21f4

      三、代碼實現

      1. 相關數學

      關于數學部分,我只進行簡要說明,不講它們的數學原理,也不過多注釋。如果你只是想將神經網絡應用到游戲中,那這部分完全可以不必深究原理,弄清它們應用的場合即可。

      a. 初始化權重函數

      神經網絡權重的初始化十分重要,它會影響你的神經網絡最后能否訓練成功。這里實現了3種典型的初始化方法:

      • 隨機初始化(std = 0.01):是比較普通的方法,深度學習新手接觸的第一個初始化方式。

      • Xavier初始化:適用于激活函數為Sigmoid和Tanh的場合。

      • He初始化:適用于激活函數為ReLU及其衍生函數,如Leaky ReLU的場合。


      這里我還用了枚舉,方便在編輯時切換初始化的方法(后續幾類數學函數也會用這種方法):

                                                                 using System;


      namespaceJufGame.AI.ANN
      {
      publicstaticclassInitWFunc
      {
      publicenum Type
      {
      Random, Xavier, He, None
      }
      public static void InitWeights(Type initWFunc, Neuron neuron)
      {
      switch(initWFunc)
      {
      case Type.Xavier:
      XavierInitWeights(neuron.Weights);
      break;
      case Type.He:
      HeInitWeights(neuron.Weights);
      break;
      case Type.Random:
      RandomInitWeights(neuron.Weights);
      break;
      default:
      break;
      }
      }
      private static void RandomInitWeights(float[] weightsList)
      {
      var rand = new Random();
      for (int i = 0; i < weightsList.Length; ++i)
      {
      //使用較小的標準差,適合普通的隨機初始化
      weightsList[i] = (float)(rand.NextGaussian() * 0.01);
      }
      }
      private static void XavierInitWeights(float[] weightsList)
      {
      var rand = new Random();
      var scale = 1f / MathF.Sqrt(weightsList.Length);
      for (int i = 0; i < weightsList.Length; ++i)
      {
      weightsList[i] = (float)(rand.NextDouble() * 2 * scale - scale);
      }
      }
      private static void HeInitWeights(float[] weightsList)
      {
      var rand = new Random();
      var stdDev = MathF.Sqrt(2f / weightsList.Length); //計算標準差
      for (int i = 0; i < weightsList.Length; ++i)
      {
      //生成服從正態分布的隨機數,并乘以標準差
      weightsList[i] = (float)(rand.NextGaussian() * stdDev);
      }
      }
      // 用于生成服從標準正態分布的隨機數的輔助方法
      private static double NextGaussian(this Random rand)
      {
      double u1 = 1.0 - rand.NextDouble(); // 生成 [0, 1) 之間的隨機數
      double u2 = 1.0 - rand.NextDouble();
      // 使用 Box-Muller 變換生成正態分布的隨機數
      return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
      }
      }
      }

      b. 激活函數

      一般神經網絡中所有隱藏層都使用同一種激活函數,輸出層根據問題需求可能會使用和隱藏層不一樣的激活函數。激活函數都有非線性且可導的特點,我也實現了一些典型的激活函數:


      • 直接輸出(Identify):不做處理直接輸出,用于輸出層。

      • Sigmoid:早期的主流,現在一般用于輸出層需要將輸出值限制在0~1的場合,或者是只有兩個輸出的二分問題。

      • Tanh:相當于Sigmoid的改造,將輸出限制在了-1~1。

      • ReLU:當今的主流激活函數,長得十分友好,甚至不用加減運算。一般選它準沒錯。

      • Leaky ReLU:ReLU的改造,使得對負數輸入也有響應,但并沒有說它一定好于ReLU。如果你用ReLU訓練出現問題,可以換這個試試。

      • Softmax:把一系列輸出轉為總和為1的小數,并且維持彼此的大小關系,相當于把輸出結果轉為了概率。適用于多分類問題,但一定要搭配交叉熵損失函數使用

                                                                 using System;

      namespaceJufGame.AI.ANN
      {
      publicstaticclassActivationFunc
      {
      private delegate float FuncCalc(float x);
      privatestatic FuncCalc curAcFunc;
      publicenum Type
      {
      Identify, Softmax, Tanh, Sigmoid, ReLU, LeakyReLU
      }
      //按層使用激活函數計算
      public static void Calc(Type funcType, Layer layer)
      {
      if(funcType == Type.Softmax)
      {
      Softmax_Calc(layer);
      }
      else
      {
      curAcFunc = funcType switch
      {
      Type.Sigmoid => Sigmoid_Calc,
      Type.Tanh => Tanh_Calc,
      Type.ReLU => ReLU_Calc,
      Type.LeakyReLU => LeakyReLU_Calc,
      _ => Identify_Calc,
      };
      for(int i = 0; i < layer.Neurons.Length; ++i)
      {
      layer.Output[i] = curAcFunc(layer.Neurons[i].Sum);
      }
      }
      }
      //根據傳入下標index選取層中神經元,并進行求導
      public static float Diff(Type funcType, Layer layer, int index)
      {
      return funcType switch
      {
      Type.Softmax => Softmax_Diff(layer, index),
      Type.Sigmoid => Sigmoid_Diff(layer, index),
      Type.Tanh => Tanh_Diff(layer, index),
      Type.ReLU => ReLU_Diff(layer, index),
      Type.LeakyReLU => LeakyReLU_Diff(layer, index),
      _ => Identify_Diff(),
      };
      }

      #region 直接輸出
      private static float Identify_Calc(float x)
      {
      return x;
      }
      private static float Identify_Diff()
      {
      return1;
      }
      #endregion

      #region Softmax
      private static void Softmax_Calc(Layer layer)
      {
      var neurons = layer.Neurons;
      var expSum = 0.0f;
      for(int i = 0; i < neurons.Length; ++i)
      {
      layer.Output[i] = MathF.Exp(neurons[i].Sum);
      expSum += layer.Output[i];
      }
      for(int i = 0; i < neurons.Length; ++i)
      {
      layer.Output[i] /= expSum;
      }
      }
      private static float Softmax_Diff(Layer outLayer, int index)
      {
      return outLayer.Output[index] * (1 - outLayer.Output[index]);
      }
      #endregion

      #region Sigmoid
      private static float Sigmoid_Calc(float x)
      {
      return1.0f / (1.0f + MathF.Exp(-x));
      }
      private static float Sigmoid_Diff(Layer outLayer, int index)
      {
      return outLayer.Output[index] * (1 - outLayer.Output[index]);
      }
      #endregion

      #region Tanh
      private static float Tanh_Calc(float x)
      {
      var expVal = MathF.Exp(-x);
      return (1.0f - expVal) / (1.0f + expVal);
      }
      private static float Tanh_Diff(Layer outLayer, int index)
      {
      return1.0f - MathF.Pow(outLayer.Output[index], 2.0f);
      }
      #endregion

      #region ReLU
      public static float ReLU_Calc(float x)
      {
      return x > 0 ? x : 0;
      }
      public static float ReLU_Diff(Layer outLayer, int index)
      {
      return outLayer.Neurons[index].Sum > 0 ? 1 : 0;
      }
      #endregion

      #region LeakyReLU
      private static float LeakyReLU_Calc(float x)
      {
      return x > 0 ? x : 0.01f * x;
      }
      private static float LeakyReLU_Diff(Layer outLayer, int index)
      {
      return outLayer.Neurons[index].Sum > 0 ? 1 : 0.01f;
      }
      #endregion
      }
      }

      c. 更新權重函數

      權重的更新涉及一些「超參數」,比如學習率、最大迭代次數等。這些參數是程序不會進行更新的,只能人工提前設置好。在神經網絡的學習中,學習率的值很重要,過小會導致訓練費時;過大則會導致學習發散而不能正確進行。但好在后面人們想出來更好的權重更新函數,它們對「超參數」的依賴會減小很多。我們所實現的有:


      • SGD:隨機梯度下降,最簡單的一種更新方法,但有時并不是這么高效,容易陷入局部最優解。

      • Movement:基于物理上的動量概念,它會在更新權重的過程中考慮先前的更新步驟,需要為每個權重設置額外參數(用m表示)來記錄「動量」。

      • AdaGrad:運用了學習率衰減的技巧,為每個權重適當地調整學習率,相當于給每個權重都設置了獨立的學習率,也需要額外參數(用v表示)記錄。

      • Adam:將Movment與AdaGrad結合了起來,通過組合二者的優點,有望實現參數空間的高效搜索。

      當然,上述4個方法各有優劣,可以優先考慮SGD和Adam。

      順帶一提,權重的更新都是建立在「梯度」之上的,「梯度」可以理解為對神經網絡整體權重的變化趨勢。你想,有這么多權重要更新,有時訓練一個樣本A后,會要求權重w0+ = 0.01、w1- = 0.05以減小誤差,但訓練下一個樣本B時,又要求w0- = 0.02、w1+ = 0.04,一個訓練集有這么多樣本,要以哪個樣本訓練時產生的權重變化為準呢?


      答案是累加每個樣本帶來的誤差并取平均值。如果覺得還不清楚,可以看看這個視頻:


      https://www.bilibili.com/video/BV16x411V7Qg/?spm_id_from=333.999.0.0&vd_source=c9a1131d04faacd4a397411965ea21f4

      還有一點,偏置b也是隨著權重更新的,它可以視為一個輸入始終為1,權重為b的權重。在后續的實現中,我將偏置放在儲存權重的列表的最后一位。(但后來才知道不提倡這種寫法。)

                                                                 using System;

      namespaceJufGame.AI.ANN
      {
      publicstaticclassUpdateWFunc
      {
      privateconstfloat MinDelta = 1e-7f;
      privateconstfloat beta1 = 0.9f;
      privateconstfloat beta2 = 0.999f;
      private delegate void UpdateLayer(Neuron curNeuron, float learningRate, int curEpochs, int samplesCount);
      publicenum Type
      {
      SGD, Momentum, AdaGrad, Adam
      }
      public static void UpdateNetWeights(Type type, NeuralNet net, int samplesCount)
      {
      UpdateLayer updateLayerFunc = type switch
      {
      Type.Momentum => Momentum_UpdateW,
      Type.AdaGrad => AdaGrad_UpdateW,
      Type.Adam => Adam_UpdateW,
      _ => SGD_UpdateW,
      };
      var curLayer = net.OutLayer;
      for(int j = 0; j < curLayer.Neurons.Length; ++j)
      {
      updateLayerFunc(curLayer.Neurons[j], net.LearningRate, net.CurEpochs ,samplesCount);
      }
      for(int i = 0; i < net.HdnLayers.Length; ++i)
      {
      curLayer = net.HdnLayers[i];
      for(int j = 0; j < curLayer.Neurons.Length; ++j)
      {
      updateLayerFunc(curLayer.Neurons[j], net.LearningRate, net.CurEpochs, samplesCount);
      }
      }
      }

      #region 各參數損失貢獻計算
      //計算各參數對損失的貢獻程度(也是各參數的變化的值)
      public static void CalcDelta(NeuralNet net, float[] input)
      {
      var lastInput = input;
      for(int i = 0; i < net.HdnLayers.Length; ++i)
      {
      var curLayer = net.HdnLayers[i];
      CalcLayerDelta(curLayer, lastInput);
      lastInput = curLayer.Output;
      }
      CalcLayerDelta(net.OutLayer, lastInput);
      }
      private static void CalcLayerDelta(Layer curLayer, float[] lastInput)
      {
      for(int j = 0, k; j < curLayer.Neurons.Length; ++j)
      {
      var curNeuron = curLayer.Neurons[j];
      for(k = 0; k < lastInput.Length; ++k)
      {
      //通過反向傳播時神經元的損失,計算每個權重的貢獻貢獻并累加
      curNeuron.WeightParams["Delta"][k] += curNeuron.Params["Error"] * lastInput[k];
      }
      //同理計算偏置的損失貢獻
      curNeuron.WeightParams["Delta"][k] += curNeuron.Params["Error"];
      }
      }
      #endregion

      #region SGD
      private static void SGD_UpdateW(Neuron curNeuron, float learningRate, int curEpochs, int samplesCount)
      {
      for(int k = 0; k < curNeuron.Weights.Length; ++k)
      {
      var gradient = curNeuron.WeightParams["Delta"][k] / samplesCount;
      curNeuron.Weights[k] -= learningRate * gradient;
      curNeuron.WeightParams["Delta"][k] = 0;
      }
      }
      #endregion

      #region Momentum
      private static void Momentum_UpdateW(Neuron curNeuron, float learningRate, int curEpochs, int samplesCount)
      {
      for(int k = 0; k < curNeuron.Weights.Length; ++k)
      {
      var gradient = curNeuron.WeightParams["Delta"][k] / samplesCount;
      curNeuron.WeightParams["m"][k] = beta1 * curNeuron.WeightParams["m"][k] - learningRate * gradient;
      curNeuron.Weights[k] += curNeuron.WeightParams["m"][k];
      curNeuron.WeightParams["Delta"][k] = 0;
      }
      }
      #endregion

      #region AdaGrad
      private static void AdaGrad_UpdateW(Neuron curNeuron, float learningRate, int curEpochs, int samplesCount)
      {
      for(int k = 0; k < curNeuron.Weights.Length; ++k)
      {
      var gradient = curNeuron.WeightParams["Delta"][k] / samplesCount;
      curNeuron.WeightParams["v"][k] += gradient * gradient;
      curNeuron.Weights[k] -= learningRate * gradient / MathF.Sqrt(curNeuron.WeightParams["v"][k] + MinDelta);
      curNeuron.WeightParams["Delta"][k] = 0;
      }
      }
      #endregion

      #region Adam
      private static void Adam_UpdateW(Neuron curNeuron, float learningRate, int curEpochs, int samplesCount)
      {
      for(int k = 0; k < curNeuron.Weights.Length; ++k)
      {
      var gradient = curNeuron.WeightParams["Delta"][k] / samplesCount;
      curNeuron.WeightParams["m"][k] = beta1 * curNeuron.WeightParams["m"][k] + (1 - beta1) * gradient;
      curNeuron.WeightParams["v"][k] = beta2 * curNeuron.WeightParams["v"][k] + (1 - beta2) * gradient * gradient;
      var mHat = curNeuron.WeightParams["m"][k] / (1 - MathF.Pow(beta1, curEpochs));
      var vHat = curNeuron.WeightParams["v"][k] / (1 - MathF.Pow(beta2, curEpochs));
      curNeuron.Weights[k] -= learningRate * mHat / (MathF.Sqrt(vHat) + MinDelta);
      curNeuron.WeightParams["Delta"][k] = 0;
      }
      }
      #endregion
      }
      }

      d. 損失函數

      損失函數用來衡量輸出與正確值之間的差距,這里實現的是最常用的兩個損失函數:

      • 均方差函數:簡單實用,形式如下:


      • 交叉熵函數:主要用在多分類問題上,配合Softmax使用:


                                                                 using System;

      namespaceJufGame.AI.ANN
      {
      publicstaticclassLossFunc
      {
      privateconstfloat MinDelta = 1e-7f;
      publicenum Type
      {
      MeanSqurad, CrossEntropy,
      }
      public static float Calc(Type type, float[] targetOut, Layer outLayer)
      {
      return type switch
      {
      Type.MeanSqurad => MeanSquradErr_Calc(targetOut, outLayer),
      _ => CrossEntropy_Calc(targetOut, outLayer),
      };
      }
      public static void Diff(Type type, float[] targetOut, Layer outLayer)
      {
      switch(type)
      {
      case Type.MeanSqurad:
      MeanSquradErr_Diff(targetOut, outLayer);
      break;
      case Type.CrossEntropy:
      CrossEntropy_Diff(targetOut, outLayer);
      break;
      };
      }

      private static float MeanSquradErr_Calc(float[] targetOut, Layer outLayer)
      {
      var errSum = 0.0f;
      for(int i = 0; i < targetOut.Length; ++i)
      {
      errSum += MathF.Pow(outLayer.Output[i] - targetOut[i], 2);
      }
      return errSum / (2 * targetOut.Length);
      }
      private static void MeanSquradErr_Diff(float[] targetOut, Layer outLayer)
      {
      for(int i = 0; i < targetOut.Length; ++i)
      {
      var curNeuron = outLayer.Neurons[i];
      curNeuron.Params["Error"] = outLayer.Output[i] - targetOut[i];
      }
      }

      private static float CrossEntropy_Calc(float[] targetOut, Layer outLayer)
      {
      var errSum = 0.0f;
      for(int i = 0; i < targetOut.Length; ++i)
      {
      //加上一個極小值再取log,放置出現log(0)報錯
      errSum -= targetOut[i] * MathF.Log(outLayer.Output[i] + MinDelta);
      }
      return errSum;
      }
      private static void CrossEntropy_Diff(float[] targetOut, Layer outLayer)
      {
      for(int i = 0; i < targetOut.Length; ++i)
      {
      var curNeuron = outLayer.Neurons[i];
      //用Output[i]的前提:神經網絡的輸出經過了softmax處理
      curNeuron.Params["Error"] = outLayer.Output[i] - targetOut[i];
      }
      }
      }
      }

      2. 感知機(神經元)


      簡單地對神經元結構進行實現,只是神經元在訓練時,需要為自身或者自身的權重記錄一些額外信息,所以多了WeightParams和Params備以記錄。

      • 為什么不記錄激活函數的計算結果out?

      因為在訓練過程中常常要以層為單位統一處理激活函數的計算結果,故而將out都記錄在層中了。(實際上Python中許多深度學習框架庫都是以層為最小單位構建神經網絡的,這有利于進行矩陣運算,但在我們的實現中,一來沒用到矩陣運算,二來是希望能讓大家更直接地看到神經網絡訓練、計算的細節,所以我們以單個神經元作為最小的單位。)

      • 為什么沒有激活函數f(x)?

      因為我們之前說過,神經網絡的隱藏層都是使用同一種激活函數,頂多輸出層用的不太一樣。也就是說我們只需要記錄兩個函數的類型,所以讓后續實現的神經網絡類記下就行了,沒必要每個神經元都記錄,浪費空間。

                                                                 using System;
      using System.Collections.Generic;
      using UnityEngine;


      namespaceJufGame.AI.ANN
      {
      [Serializable] // 方便在編輯器頁面查看
      publicclassNeuron
      {
      //神經元權重列表,末位放置偏置b
      publicfloat[] Weights => weights;
      //加權和
      publicfloat Sum => sum;
      //為各個權重分配的額外參數
      public Dictionary WeightParams{ get; privateset; }
      //為神經元本身分配的額外參數
      public Dictionary Params{ get; privateset; }
      [SerializeField]privatefloat[] weights;
      privatefloat sum;
      public Neuron(int weightCount)
      {
      weights = newfloat[weightCount + 1];//末尾放偏置
      }
      ///
      /// 初始化訓練所需參數列表,僅在訓練時調用
      ///
      public void InitCache()
      {
      Params = new Dictionary
      {
      ["Error"] = 0,//該值用來記錄,每次更新時的累計損失
      };
      WeightParams = new Dictionary
      {
      //記錄權重待變化值
      ["Delta"] = newfloat[weights.Length],
      //Momentum和Adam中,用于記錄權重變化的「動量」
      ["m"] = newfloat[weights.Length],
      //AdaGrad和Adam中,用于記錄權重獨立學習率
      ["v"] = newfloat[weights.Length],
      };
      }
      //計算Sum
      public float CalcSum(float[] input)
      {
      int i;
      sum = 0;
      for(i = 0; i < input.Length; ++i)
      {
      sum += weights[i] * input[i];//加權和
      }
      sum += weights[i];//加上權重
      return Sum;
      }
      }
      }

      3. 層

      沒有太多必要說的,就是嵌套調用了包含的各神經元的函數,比如層的計算就是各個神經元的計算,其它同理。

                                                                 using System;
      using UnityEngine;


      namespaceJufGame.AI.ANN
      {
      [Serializable]
      publicclassLayer
      {
      public Neuron[] Neurons => neurons;//存儲神經元
      publicfloat[] Output => output;//存儲各神經元激活函數輸出
      [SerializeField] private Neuron[] neurons;
      [SerializeField] privatefloat[] output;
      public Layer(int neuronCount)
      {
      output = newfloat[neuronCount];
      neurons = new Neuron[neuronCount];
      }
      //對層中的每個神經元的權重進行初始化
      public void InitWeights(int weightCount, InitWFunc.Type initType)
      {
      for(int i = 0; i < neurons.Length; ++i)
      {
      neurons[i] = new Neuron(weightCount);
      InitWFunc.InitWeights(initType, neurons[i]);
      }
      }
      //初始化層中每個神經元的額外參數
      public void InitCache()
      {
      for(int i = 0; i < neurons.Length; ++i)
      {
      neurons[i].InitCache();
      }
      }
      //計算該層,實際上就是計算所有神經元的加權和,并求出激活函數的輸出
      public float[] CalcLayer(float[] inputData, ActivationFunc.Type acFuc)
      {
      for(int i = 0; i < neurons.Length; ++i)
      {
      neurons[i].CalcSum(inputData);
      }
      ActivationFunc.Calc(acFuc, this);
      return output;
      }
      }
      }

      4. 多層感知機(神經網絡)

      神經網絡也一樣,是對層的各個功能的再度包裝,只是多了些超參數成員變量。

      • 為什么沒有輸入層?

      因為輸入層其實就只是輸入的數值,沒必要單獨設置一個層(Python中許多深度學習框架也是這樣的)。

      • 怎么讀取輸出?

      直接讀取輸出層的輸出列表即可。

                                                                 using System;
      using UnityEngine;

      namespaceJufGame.AI.ANN
      {
      [Serializable]
      publicclassNeuralNet
      {
      publicfloat TargetError = 0.0001f;//預期誤差,當損失函數的結果小于它時,就停止訓練
      publicfloat LearningRate = 0.01f;//學習率
      publicint CurEpochs;//記錄當前迭代的次數
      public ActivationFunc.Type hdnAcFunc;//隱藏層激活函數類型
      public ActivationFunc.Type outAcFunc;//輸出層激活函數類型
      public Layer[] HdnLayers => hdnLayers;//隱藏層
      public Layer OutLayer => outLayer;//輸出層
      [SerializeField] private Layer[] hdnLayers;
      [SerializeField] private Layer outLayer;

      public NeuralNet(int hdnLayerCount, int[] neuronsOfLayers, int outCount,
      ActivationFunc.Type hdnAcFnc, ActivationFunc.Type outAcFnc,
      float targetError = 0.0001f, float learningRate = 0.01f)
      {
      outLayer = new Layer(outCount);
      hdnLayers = new Layer[hdnLayerCount];
      for(int i = 0, j = 0; i < hdnLayerCount; ++i)
      {
      hdnLayers[i] = new Layer(neuronsOfLayers[j]);
      }
      hdnAcFunc = hdnAcFnc;
      outAcFunc = outAcFnc;
      TargetError = targetError;
      LearningRate = learningRate;
      }
      //初始化各神經元權重
      public void InitWeights(int inputDataCount, InitWFunc.Type initType)
      {
      int neuronNum = inputDataCount;
      for(int i = 0; i < HdnLayers.Length; ++i)
      {
      hdnLayers[i].InitWeights(neuronNum, initType);
      neuronNum = HdnLayers[i].Neurons.Length;
      }
      outLayer.InitWeights(neuronNum, initType);
      }
      //初始化各神經元額外參數列表
      public void InitCache()
      {
      for(int i = 0; i < HdnLayers.Length; ++i)
      {
      hdnLayers[i].InitCache();
      }
      outLayer.InitCache();
      }
      //計算神經網絡
      public float[] CalcNet(float[] inputData)
      {
      var curInput = inputData;
      for(int j = 0; j < hdnLayers.Length; ++j)
      {
      curInput = hdnLayers[j].CalcLayer(curInput, hdnAcFunc);
      }
      return outLayer.CalcLayer(curInput, outAcFunc);
      }
      }
      }

      至此,神經網絡就搭建完成了,并沒有想象的那么復雜。

      5. 訓練器

      先實現一個訓練器的基類……等等,明明就一種神經網絡,為什么還要有基類,直接寫不好嗎?

      其實最初是打算實現多種神經網絡的,但后來考試臨近,不得不轉移重心,最終只實現了最簡單的MLP。Unity本身也已經可以導入ONNX模型,如果要在游戲里想實現圖像識別這類復雜功能的話,導入模型似乎更方便,所以實現更多神經網絡的必要性就值得考慮了。當然,這些文末會再進行討論。

      先來看看這個基類有哪些東西:

                                                                 using UnityEngine;

      namespaceJufGame.AI.ANN
      {
      publicabstractclassTraining
      {
      public NeuralNet TrainingNet;//需要訓練的神經網絡
      publicfloat[][] InputSet => inputSet;//訓練輸入集

      /*沒有「訓練輸出集」是因為并非所有類型的神經網絡都需要「訓練輸出」
      所以它不是基類必需的,當然,這些就是題外話了*/

      protectedfloat[][] inputSet;
      [SerializeField] protectedint maxEpochs;//最大迭代次數

      public Training(NeuralNet initedNet, int maxEpochs)
      {
      this.maxEpochs = maxEpochs;
      TrainingNet = initedNet;
      }

      public void SetInput(float[][] inputSet)//設置訓練輸入集
      {
      this.inputSet = inputSet;
      }
      public abstract bool IsTrainEnd();//是否訓練完成
      public abstract void Train(); //不斷訓練神經網絡
      public abstract void Train_OneTime();//訓練(迭代)一次神經網絡

      //打印神經網絡輸出的結果,調試用的
      public static void DebugNetRes(NeuralNet net, float[][] testInput)
      {
      for(int i = 0; i < testInput.GetLength(0); ++i)
      {
      var res = net.CalcNet(testInput[i]);
      for(int j = 0; j < res.Length; ++j)
      {
      Debug.Log("檢驗結果 " + i + " = " + res[j]);
      }
      }
      }
      }
      }

      最后,就是真正用來訓練的類了,我們將采用最常見梯度下降法進行訓練。其中涉及前向傳播和反向傳播,我稍作解釋:

      • 前向傳播(Forward Propagation):傳入訓練輸入樣本計算出當前神經網絡模型的輸出,并進一步計算損失(損失函數的計算結果其實在反向傳播中并沒有用,只是給開發者看的,用來判斷當前訓練情況)。

      • 反向傳播(Backward Propagation):從損失函數開始,用鏈式求導法則,反向(輸出層?隱藏層?輸入層)計算每個神經元的損失(下圖中的δ、代碼中的Params["Error"])。通過神經元的損失,可以計算出神經元的每個參數(權重、偏置)的損失貢獻(權重更新函數代碼中的WeightParams["Delta"])并一直累加,直到訓練集被讀取完。這時,我們就說完成了一次訓練迭代


      完成一次迭代后(不是訓練完一個樣本后),將累加的損失除以訓練樣本數取得均值,再用權重更新函數對各參數進行更新。

      這一迭代過程反復進行,直到損失函數計算的誤差達到可容許范圍(也就是小于預期損失)或達到最大訓練次數,詳情可看《解讀反向傳播算法(圖與公式結合)》[1](但注意!該文章為了方便講解,訓練完一個樣本就開始更新權重了),實現如下:

                                                                 using UnityEngine;


      namespaceJufGame.AI.ANN
      {
      [System.Serializable]
      publicclassBPNN : Training
      {
      publicfloat[][] OutputSet => outputSet;
      [SerializeField] privatefloat meanError = float.MaxValue;
      [SerializeField] private LossFunc.Type errorFunc;
      [SerializeField] private UpdateWFunc.Type updateWFunc;
      privatefloat[][] outputSet;
      public BPNN(NeuralNet initedNet, LossFunc.Type errorFunc, UpdateWFunc.Type updateWFunc, int maxEpochs): base(initedNet,maxEpochs)
      {
      this.errorFunc = errorFunc;
      this.updateWFunc = updateWFunc;
      }
      public void SetOutput(float[][] outputSet)
      {
      this.outputSet = outputSet;
      }
      public override bool IsTrainEnd()//判斷是否訓練完成
      {
      return meanError < TrainingNet.TargetError
      || maxEpochs < TrainingNet.CurEpochs;
      }
      public override void Train()
      {
      meanError = float.MaxValue;
      while(!IsTrainEnd())
      {
      Train_OneTime();
      }
      }
      public override void Train_OneTime()
      {
      int samplesCount = inputSet.GetLength(0);//記下樣本數量
      ++TrainingNet.CurEpochs;//更新迭代次數
      meanError = 0;
      for(int i = 0; i < samplesCount; ++i)
      {
      ForWard(i);
      Backpropagation();
      UpdateWFunc.CalcDelta(TrainingNet, inputSet[i]);
      }
      UpdateWFunc.UpdateNetWeights( updateWFunc, TrainingNet, samplesCount);
      meanError /= samplesCount;//取樣本誤差均值作為本次迭代的誤差
      #if UNITY_EDITOR
      Debug.Log($"誤差:{meanError}");//調試時用的
      #endif
      }
      private void ForWard(int trainIndex)
      {
      var outLayer = TrainingNet.OutLayer;
      TrainingNet.CalcNet(inputSet[trainIndex]);
      meanError = LossFunc.Calc(errorFunc, outputSet[trainIndex], outLayer);
      /*這里圖省事,將反向傳播的第一步一并計算了*/
      LossFunc.Diff(errorFunc, outputSet[trainIndex], outLayer);//損失函數求導
      for(int i = 0; i < outLayer.Neurons.Length; ++i)//輸出層激活函數求導
      {
      outLayer.Neurons[i].Params["Error"] *= ActivationFunc.Diff(TrainingNet.outAcFunc, outLayer, i);
      }
      }
      private void Backpropagation()
      {
      var lastLayer = TrainingNet.OutLayer;
      for(int i = TrainingNet.HdnLayers.Length - 1; i > -1; --i)
      {
      var curLayer = TrainingNet.HdnLayers[i];
      for(int j = 0; j < curLayer.Neurons.Length; ++j)
      {
      var curNeuron = curLayer.Neurons[j];
      //每次計算損失時要清零,避免上次迭代結果產生的干擾
      curNeuron.Params["Error"] = 0;
      for(int k = 0; k < lastLayer.Neurons.Length; ++k)
      {
      var lastNeuron = lastLayer.Neurons[k];
      curNeuron.Params["Error"] += lastNeuron.Params["Error"] * lastNeuron.Weights[j];
      }
      curNeuron.Params["Error"] *= ActivationFunc.Diff(TrainingNet.hdnAcFunc, curLayer, j);
      }
      lastLayer = curLayer;
      }
      }
      }
      }

      四、使用教程

      一切都準備就緒了,那要怎么運轉這個神經網絡呢?我們創建一個繼承了MonoBehavior的腳本,并聲明下面三個公開的字段:

                                                                 using UnityEngine;
      using JufGame.AI.ANN;


      public class TrainANN : MonoBehaviour
      {
      public int inputCount;
      public BPNN bp;
      public InitWFunc.Type initW;
      }

      將它掛載在場景的任一物體上,不出意外的話,你可以在編輯器看到神經網絡類的許多關鍵變量都可以顯示出來(如果你的沒有,就要注意是否遺漏[System.Serializable]或設置成了私有類):


      我們再完善下腳本,設置好訓練輸入和輸出(以「異或」運算為例),使得神經網絡能在Unity運行時逐幀訓練:

                                                                 public classTrainANN : MonoBehaviour
      {
      publicint inputCount;
      public BPNN bp;
      public InitWFunc.Type initW;

      privatefloat[][] inSet = //異或運算的輸入
      {
      newfloat[]{1, 0},
      newfloat[]{1, 1},
      newfloat[]{0, 0},
      newfloat[]{0, 1},
      };
      privatefloat[][] outSet = //異或運算的輸出
      {
      newfloat[]{1},
      newfloat[]{0},
      newfloat[]{0},
      newfloat[]{1},
      };

      private void Awake()
      {
      ...

      特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

      Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

      相關推薦
      熱點推薦
      阿那亞偶遇何穗,本人好瘦好高白到發光,她跟保姆帶娃不見陳偉霆

      阿那亞偶遇何穗,本人好瘦好高白到發光,她跟保姆帶娃不見陳偉霆

      手工制作阿殲
      2026-07-17 02:12:46
      危險信號釋放:銀行利率這次可能不是鬧著玩的

      危險信號釋放:銀行利率這次可能不是鬧著玩的

      愛看劇的阿峰
      2026-07-18 13:28:37
      啼笑皆非!網友向LV客服舉報廁所窗戶花紋,提供現場證據,該聊天截圖引發熱議

      啼笑皆非!網友向LV客服舉報廁所窗戶花紋,提供現場證據,該聊天截圖引發熱議

      火山詩話
      2026-07-18 06:29:58
      外媒:特朗普將出席世界杯決賽,F-16戰機、軍方狙擊手及數千名特工被調集展開安保行動

      外媒:特朗普將出席世界杯決賽,F-16戰機、軍方狙擊手及數千名特工被調集展開安保行動

      環球網資訊
      2026-07-19 13:00:17
      37歲董事長溺亡,水坑里活活憋死,救援被曝拖延,更多內幕曝光

      37歲董事長溺亡,水坑里活活憋死,救援被曝拖延,更多內幕曝光

      千言娛樂記
      2026-05-30 15:01:40
      心照不宣地放任失業,是大國經濟崩盤的根源?

      心照不宣地放任失業,是大國經濟崩盤的根源?

      文化縱橫
      2026-07-14 16:45:46
      10萬美元“綠卡保證金”?特朗普政府再揮大棒,親屬移民或面臨“天價門檻”!

      10萬美元“綠卡保證金”?特朗普政府再揮大棒,親屬移民或面臨“天價門檻”!

      留學生日報
      2026-07-19 19:19:47
      今日開始!央一央八愛奇藝優酷等6部大劇上新!陣容不錯,看哪部

      今日開始!央一央八愛奇藝優酷等6部大劇上新!陣容不錯,看哪部

      陳意小可愛
      2026-07-19 11:15:37
      “真想踢他一頓”,父親公開厭惡1米83兒子:每天食堂要吃一百多

      “真想踢他一頓”,父親公開厭惡1米83兒子:每天食堂要吃一百多

      世界圈
      2026-07-14 10:52:34
      逼成下一個倫納德?馬刺迪倫?哈珀下賽季角色定位傳來重磅消息

      逼成下一個倫納德?馬刺迪倫?哈珀下賽季角色定位傳來重磅消息

      夜白侃球
      2026-07-19 19:28:29
      做個人!35歲坎特0出場告別世界杯,德尚賽后摸頭安撫坎特面露微笑

      做個人!35歲坎特0出場告別世界杯,德尚賽后摸頭安撫坎特面露微笑

      818體育
      2026-07-19 11:46:30
      姆巴佩殺瘋了!本屆8場10球領先梅西2球 世界杯歷史22球超梅西1球

      姆巴佩殺瘋了!本屆8場10球領先梅西2球 世界杯歷史22球超梅西1球

      風過鄉
      2026-07-19 06:44:28
      看爽了!世界杯季軍戰轟出網球比分:兩隊轟38腳進10球 全場0黃牌

      看爽了!世界杯季軍戰轟出網球比分:兩隊轟38腳進10球 全場0黃牌

      風過鄉
      2026-07-19 07:15:56
      日本人守護了40年的K-Car自留地,被一臺中國小車+國民女星敲了門

      日本人守護了40年的K-Car自留地,被一臺中國小車+國民女星敲了門

      東京在線
      2026-07-18 19:05:16
      馬龍/許昕三奪乒乓球全錦賽男雙冠軍

      馬龍/許昕三奪乒乓球全錦賽男雙冠軍

      蓬勃新聞
      2026-07-19 19:49:26
      常來常往的男女,越過以下3條“紅線”,關系就曖昧了

      常來常往的男女,越過以下3條“紅線”,關系就曖昧了

      葉飛飛情感屋
      2026-07-19 19:10:07
      老了才明白:只有一個兒子的家庭,兒媳再好,也別交出這4樣東西

      老了才明白:只有一個兒子的家庭,兒媳再好,也別交出這4樣東西

      匹夫來搞笑
      2026-06-28 18:22:45
      不管奧德賽評價如何,查理茲·塞隆都贏了!

      不管奧德賽評價如何,查理茲·塞隆都贏了!

      新民周刊
      2026-07-19 15:06:41
      你知道10萬塊錢的網貸有多可怕嗎?

      你知道10萬塊錢的網貸有多可怕嗎?

      流蘇晚晴
      2026-07-11 16:29:42
      這很科學:89%的人幻想過同時和多人發生性行為,算精神出軌嗎?

      這很科學:89%的人幻想過同時和多人發生性行為,算精神出軌嗎?

      宇宙時空
      2026-05-26 18:20:10
      2026-07-19 20:23:00
      侑虎科技UWA incentive-icons
      侑虎科技UWA
      游戲/VR性能優化平臺
      1599文章數 987關注度
      往期回顧 全部

      科技要聞

      Kimi K3單項登頂 整體落后前沿模型2-3個月

      頭條要聞

      伊朗導彈襲擊致2名美軍人員死亡 特朗普回應

      頭條要聞

      伊朗導彈襲擊致2名美軍人員死亡 特朗普回應

      體育要聞

      世界杯決賽,從“澡盆德比”500年前講起

      娛樂要聞

      王侃因病逝世 兩年前與父親牛犇同臺

      財經要聞

      任澤平VIP會員自稱爆倉巨虧千萬

      汽車要聞

      把中國超跑賣到英國,比亞迪正在被世界看見

      態度原創

      房產
      健康
      教育
      藝術
      軍事航空

      房產要聞

      突然出手!千畝城更+一線江景,世嘉亮出超級四代宅!

      刮痧也會刮出腦梗?講個真實案例

      教育要聞

      不少小朋友一看到圖都怕了,更別說看清題意了!

      藝術要聞

      33億全款!拼多多拿下陸家嘴老樓,被吐槽“太樸實”

      軍事要聞

      兩名美軍被伊朗炸死 特朗普:令人難過

      無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 美丽贺岁| 落魄的贵族琉璃川观看| 一块五花肉错位关系| 徐锦江电影| 苍老师100部经典电视剧免费观看 三年成全免费看高清影视大全 | 缘之空无删减资源在线| 青平梅电影| 妹妹轮到你了韩剧| 扒鸭屁股| 叔嫂恋2之血恋免费普通话| 超精英陆战队| 仙剑4结局动画| 《法国空姐2024版》完整版在线播放| 爱丽丝学园邪恶彩画| 初体验五| 绣春刀3无间炼狱免费观看| 叶问前传| 新锅第一次用怎么用什么方法开锅| 艰难爱情全集| 《伊波拉病毒》普通话| 2020中国好声音| 坠落的女律师在线观看完整版| 等一个晴天说再见| 史泰龙电影大开杀戒| 金刚狼电影| 《克里斯蒂娜》电影在线观看| 希拉里演讲| 二十一世纪性格爱情指南第4集| 长安三万里提前抢先看完整版| 1983版高山下的花环| 电视剧长相思在线观看免费 | 美国版武则天惊艳版| 民工 陈思成| 阿拉伯电影绝色在线观看免费| 境界的轮回| 法国版《女超人:麦乐迪》是不是黄 | 美姊妹赤板栗中字完整版| 刺杀游戏2011| 彪悍少年高清| 绝种好男人国语| 大侦探9免费观看| 老九门西瓜| 电影需要爸爸播种子完整版免费在线观看 | 出轨的女人完整版| 孤独的美食家第五季| 特殊的快递员| 庆余年免费手机全集在线观看| 逆罪电视剧| 密室大逃脱第四季免费观看完整版 | 除暴电影在线高清观看 | 希特勒回来了电影| 沙场归来宠家:女战神短剧全集| 迷人的保姆免费观看| 日本电影特殊治疗的医护| 高清《这份恋情望你察觉》| 武直15| 特殊游泳私人教练在线观看| 忘川彼岸| 踏血寻梅电影| 黄色仓库在线播放| 刘老根第一部| 酒店1-100集全集免费| 儿子的妻子8免费观看电视剧| 天地无伦 快播| 公与3个媳HD免费| 视频免费观看高清| 傻妃狠嚣张| 天海翼视频在线播放| 杀狼花全集在线观看| 甘孜| 生活妆画法| 新建文件夹在线观看完整版| 需要爸爸播种种子在线观看 | 树下红房子电视剧免费版在线观看| 沉香如屑电视剧免费观看55集| 霹雳布袋戏刀龙传说| 鸟鸟主持星光大赏| 《需要爸爸播种子》电影免费在线观看,高清完整版美国倫理片_努努影院 | 黎明2011红馆演唱会| 魔力看在姨母家的客厅| 生化危机3 电影| 女人公敌高清第三季| 高压默然| 连诗雅喜爱夜蒲| 钢壳都市| 迷情客栈韩国未删减版| 男得有爱电影完整版| 爱我几何完整版| 本色电视剧免费版观看| 女超人麦乐迪满天星| 杉杉来了 电视剧| 加索尔三兄弟| 品味人生坎贝奇在线观看完整版| 被四个人折磨8小时| 少儿不宜美国电影| 商务旅行戴帽子的老板同行电影完整版| 谁的青春不乱爱| 我们的宇宙泰剧免费观看| 让子弹飞隐藏剧情| 奥特曼八兄弟国语版| 爱丫爱丫在线国语高清电影| 苹果电影免费完整版在线观看影片 | 桥矿国语1普通话版| 切糕段子| SSIS-338《最高の爱人沼》在哪看| 如果爱第2季 综艺| 久保同学不放过| 茶界中国| 新白发魔女传女主角| 冲上云霄1主题曲| 安娜1980满天星英文| 美国版《丛林女超人》| 猫和老鼠四川版全集| 禁忌1-4保罗一家| 白蛇1:白蛇缘起| 白发电视剧| 李宇春 新专辑| 不磨灭的番号| 爸爸要播种子美国电视剧在线观看| 珠光宝气国语版| 新金瓶梅龚玥菲版| 铿铿锵铿锵锵锵免费观看| 凉森玲梦| 姐妹牙医赤子板栗| 真爱墨菲定律泰剧在线观看| 哪吒3上映时间| 左右不逢源第四季| 纵棠短剧演员表| 超级整蛊霸王国语版| 在线观看黑狐| 爱情悠悠药草香剧情分集介绍| 欲望学园在线观看| 陪你到世界之巅免费观看全集| 面具安七炫| 丰满的临时工HD| 瘦身男女粤语| 需要爸爸播种种子在线观看 | 双人调情按摩术免费观看| 了不起的埃莉诺电影| 法国监狱的电影| 国语韩剧媳妇当家| 听海哭的声音是什么歌| 韩剧我的妈妈| 天使禁恋| 电视剧大西南剿匪记| 无尽之剑宝石| 斗罗大陆176免费完整观看| 白色橄榄树全集免费观看完整版| 科技| 绝地反击| 永兴县| 斗罗大陆126| 私人航空2法国电影| 《丰满女朋友》在线观看| 法警小队 第一季未删减| 《法国空乘11》播放| 高清《星汉灿烂》全集免费看| 乘风2026| 旋风管家 朴信惠| 高清命悬一枪未删减| 大乐透24142期开奖结果| 角斗英雄| 警方回应山东父子三人落水溺亡| 黑白配HD1080国语电影下载地址| 桂宝之爆笑闯宇宙| 妻子日本电影| 杜淳说绝不允许女儿出国上学| 尹律新剧《债务妻子》结局| 《奔跑吧兄弟》第四季| 《黑色电话2》| 战狼6高清国语版免费观看在线播放 | 老妇人plus60| 三年成全电影免费大全在线| 《卿本佳人》李月仙| 女超人满天星版免费观看| 鬼灭之刃游郭篇免费观看| 《法国少女》满天星| 真命天子朱元璋在线观看| 皮皮鲁与鲁西西之罐头小人| 豪华列车3万元票价值吗| 雪中悍刀行第二部32集在线观看| 唐宫美人天下| 电锯惊魂7高清版| 怪盗女谍之天海翼免费播放下载| 川村千里| 二阶堂完整版在线观看| 黑白潜行| 男人女人一起愁愁愁电视剧观看在| 穿越医妃:医术惊世短剧全集| 姐妹花爱上我| 《新生》完整版在线播放| 武直15| 邻家娇妻| 肉肉的小黄车| 安吉拉怀特在线观看| 杨鸣曾数次起诉离婚| 怒火重案国语在线观看| 高清《父母爱情》全集观看| 为爱坚守电视剧全集免费观看| 动漫解说一口气看完| 推油韩国电影| 梦幻西游服战直播| 《销售的秘密》2| 日喀则怎么读| 成版《壮志凌云》| 梦魇绝镇第二季| 马龙王楚钦等15人获记大功奖励| 50岁胖女人免费观看电视剧 | 久保同学不放过第一季免费观看| 电影《特邀外卖员无码| 王者清风电视剧| 《玉女心经3之阴阳和合》免费舒淇 | 狗剩快跑全集在线免费观看| 魔幻手机| 出差与部长同房希岛爱里在线电影 | 七情六欲韩国版| 娃娃脸3小女孩| 让子弹飞电影完整| 高清火遮眼未删减| 欧美黑白配在线| 玩偶姐姐在哪里看| 联谊对象是RS系警官| 袁阔成水泊梁山| 维修师傅的遇艳在线观看伦理片| 古惑仔4国语高清| 天龙八部胡军版插曲| 正在播放: JUL-601 你真的是处男吗?因为发现丈夫出轨被自称童贞的男子欺骗身 | 《昭和》电影免费观看| 《凶器》| 女保险公司的推销员8中| 亮剑的主题曲| 金银岛电影| 善良的小姨子在线播放| 和讨厌的部长出差在线| 高清权力王朝:默多克家族| 地藏传奇| 食物链删除的片段原声| 伽玛刀治疗肝癌| MSD-005 欧妮 维修工的心跳艳遇(下) 疼痛凌辱高潮快感-在线观看-338TV | 《古墓神蛇》| 酒店激战第1-5集在线观看人生第一次| 太极拳八段锦教学视频| 张大仙全球首吐| 大邑县| 色即是空1 qvod| 特激外卖员| 极限追捕| 如皋市| 民主之澜| 《巡回检察组》在线观看| 宾馆男人强奸女人电影| 广场舞故乡的云| 《需要爸爸播种》全集演员表| 吉星高照第二季短剧| 突然的喜欢一共多少集| 神雕侠侣电影| 《安吉拉怀特》大尺度在线观看| 女人何苦为难女人全集| 真情无限之继母电视剧| 锵锵锵锵锵游戏免费完整观看| 斗罗大陆漫画77| 高清《八仙子过大年》| 韩版恶作剧之吻02| 熊出没动漫| 李宗瑞1—72集在线观看| 妈妈电影免费观看高清电视剧| 成全动漫观看免费高清动漫| 不是冤家不聚头| 高清望向太阳未删减| 突围免费完整版在线观看| 法国女版《壮志凌云》| 斗罗大陆剧场版 剑道尘心全集观看| 寂静岭2电影| 意大利《八尺夫人》电影全集免费观看| 甜蜜惩罚13| 至上之法| 鹦鹉杀免费完整观看高清| 哇嘎成人电影| 《灰猎犬号》国语版| 滚石30年台北演唱会| 韩国售楼小姐电影在线观看完整版 | 杨思敏1一5集手机版| 长相思2在线观看全集免费播放| 漂亮的岳 《三浦步美| 地道英雄全集| 善良的嫂子4在线观看| 落魄的贵族琉璃川免费观看动漫 | 露水夫妇| 陪你一起好好吃饭在线观看| 《麦乐迪女超人》完整版美国| 谍影重重6完整高清在线播放| 愉悦与痛苦在线| 动感之星之妖精168集全集| 方糖映画| 船长哈洛克| 《大器晚成第二季》电视剧| 徐元《保险推销员》在线| 霜花店在线观看完整版电影 | 6680新视觉电影免费观看| 超人hr版| 《特殊面试2》| 特殊诊所完整版中文版| 陕西卫视回看| 青春派 电影完整版| 斗破苍穹157集免费观看动漫| 日剧《穿睡衣下楼取快递》| 土匪传奇 电视剧| 点燃我温暖你更新时间| 哇嘎电影在线播放免费版 | 《神探狄仁杰2》电视剧| 哇嘎免费观看完整版高清在线观看| 邵氏电影《聊斋奇谭》| 电影观看免费观看完整版高清| 潘迎紫版神雕侠侣| 虐爱电视剧| 浦县| 奥密克戎引“超级熔断”| 韩剧爱情中毒| 《需要爸爸播种子》免费观看在线播放_HD/未删减_1080P高清电影完整版 - 天堂 | 男按摩在线观看中文字幕| 欲把老师变舅妈| 在暴雪时分电视剧全集免费观看| 我的父亲是板凳 电视剧| 七个少女在瑞士农场的电影| 触及真心| 木下零零子电影网| 降头在线观看| 私人航空在线观看 | 熊出没之熊心归来 电影| 我要成名免费| 保定女找男| 金牌销售的秘密3hd中字| 欢乐家长群2电视剧免费播放完整版| 东北往事之黑道风云20年21集| 巴黎初体验| 1995年意大利| 好妈妈8| 命中注定我爱你剧情介绍 | 粟裕大将| 大江大河第二部| 俄罗斯版女儿国2| 手足情电视剧| 风起的日子笑看落花| 怎么自制果冻笔| 《哺乳》电影完整版观看免费视频| 动感小站动感之星第134集妖精大结局 | 看电视剧不惑之旅40集| 梅西疑似脚踝隆起大包| 小千的开发日记在线播放免费| 阿凡提之奇缘历险| 刘大明白改命师全集| 亮剑的主题曲| 最新凤凰军事观察室| 《天字号监狱》电影在线观看免费高清 | 指尖传出真挚的热情| 《需要爸爸播种籽2》| 和表姐同居免费观看| 美国式的家庭禁忌免费看| 《和讨厌的部长出差旅》电影| 亲爱的热爱的第二季| 《魔鬼天使》徐若瑄酒吧跳舞是第几| 锁清秋电视剧| 迷人的保险销售员| 兼并电视剧在线观看| 在线观看我的性感老师| 使徒行者2在线播放| 哪吒之魔童降临| 混血儿的摇篮曲| 新任女教师的凌脣教室bd| 仙逆108集完整版在线观看| 亦舞之城电视剧在线观看| 日韩大片电视剧在线哇嘎| 新扎师妹2粤语| 甜蜜惩罚2我是看守专用宠物| 明成皇后国语版| 酒店激战2免费观看国语版| 前抛实心球| 办公室秘书电影| 剑河县| 男生女生一起愁愁愁电视剧观在现观看 | 夫妻那些事全集免费观看电视剧| 巜激情丛林〉陈雅伦| 爱情公寓43| 天地粮人电视剧| 九重紫连续剧| yellow在线免费观看视频| 京华烟云 电视剧| 高清《钢铁森林》电视剧| 度华年免费看| 性影院| 心世界奇遇| 电影 搜索| 家庭教师59| 哺乳期能烫头发吗| 全红婵今日10米决赛直播| 欧少女16集全怎么看| 趟过男人河的女人电视连续剧| 夏家三千金27| 《特战荣耀》在线播放| XXXX69| 巨神战击队玩具| 冰封之城| 博格丹和女飞行员徐枫灿合影敬礼| 夕颜夫人电影| 调教小秘书| 李梦出轨| 双人按摩调情术免费观看完整版| 临终千言| 当我飞奔向你全集免费看| 美容院的待遇服务6| 最后一枪全集在线观看| 后营露营第二季无增减| 奇门遁甲2017在线观看| 《急诊护士》电影在线观看 | 高清《大蛇王》在线观看| 讨厌前男友一起出差| 意大利空乘4美版免费观看| 那金花的女婿| 摩登家庭6 电视剧| 贝拉1980在线观看| 霜花店电影在线观看免费高清版| 少女大人免费完整版高清| 龙之吻剧情| 美容院特殊待遇电视剧| 心动的信号第七季免费观看| 看了又看第三部| 小水蜜桃电视剧在线观看爱丫爱丫 | 为什么黛玉是裸死| 我是歌手 谭晶| 棘手狂情电影在线观看| 《他过分野》短剧全集完整免费| 野花日本免费高清7| 重生之商业帝国| 《女超人麦乐迪》免费播放| 使徒行者2在线播放| 你好李焕英免费版在线播放| STAR471电影免费观看中字 | 李冰冰自曝分手| 色情玉蒲之玉女心经电影| 珠海航展2016时间表| 玉女心经3之阴阳结合免费观看电视剧| 香烟爱上火柴dj| 我的好兄弟8免费观看高清| 如也小说| 水电工艳遇麻豆电影| 电影(昭和战争)越南女子别动队| 丰满胸大的在线| 跛豪叶子楣| 日本维修工的艳遇3| 全境封锁电影完整| 韩剧《财阀儿媳》| 霜花店 完整版优酷| 美发美容按摩3| 千与千寻| 大梦归离电视剧免费观看| 将军夫人全集免费| 铜锵锵锵锵锵在线观看| 超银河传说大电影| 聊斋艳谭艳梅在线观看| 叛逆者大结局43集在线播放| 《诱人的护士3》在线观看| 魔盒电影网| 电影金光大道| 小姨子在线免费观看| 蓝色隐身帽在线观看免费高清| 廖凡山河无名免费播放| 玫瑰的故事在线免费| 致美丽的你第三集| 4法国少女| 美味快递员的特殊待遇在线观看| 呼唤生活俄剧| 我和大佬第二季免费看完整版 | 《璀璨人生》在线观看| 幸福的旅程| 紫钗奇缘粤语高清| 暴躁少女免费看完整版| 苹果范冰冰电影下载| 疯狂动物城衍生剧在线观看| 琅琊榜2风起长林| 男女生一起愁愁愁电视剧在线观看 漫画 | 站着再来一次第26集| 《黑土无言》电影免费观看| 李采潭主演的电影在线视频播放| 田野上的白发 阅读答案| 1994年意大利赛伦渔夫和他的妻子| 男童虐杀3只奶猫后续| 狼行天下之钦差| 干柴烈火2免费观看电视剧| 郝板栗《千金与佣人》| 苍老师电视剧36集全免费播放| 还珠格格新版| 星空在线观看免费高清| 白夜追凶21| 炸天小姐1983完整版在线看| 挟洋自重| 阿娇不雅视频| 老亮剑全集| 职场女秘2免费观看| 军妓慰安妇| 空姐电视剧韩国| 韩剧松药店的儿子们| 琉璃全集| 男女一起愁愁愁电视剧| 巡回检察组45集免费观看| 新金瓶梅中文版真人| 我的妻子韩剧| 满天星《炎热的家庭》播放| 电影《韩国财阀》免费观看| 《羞耻(克罗地亚版) 第三季》| 芭娜娜现场| 不可撤销| 麦迪逊女超人| 狄仁杰断案传奇全集| 罗小黑战记第二季| 四个男人要轮流练瑜伽是什么电影| 让子弹飞电影| 放飞美丽mv| 韩国女战狼| 《罪人》| 成均馆绯闻10| 亲爱的朋友们| 金银瓶1-5集免费观看第9季| 家公偷看儿媳妇是什么心理| 顶楼的大象 百度影音| 央视元宵| 除却巫山无删减版txt| 酒店1-80集全集在线观看| 高清《欲海双艳》未删减| 电视剧热爱| 大象席地而坐| 木下电影电影免费播放| 免费vip电影电视剧| 回龙观星美影城影讯| 电影朋友的妈妈在线观看| 香醇绣感2普通话版剧情介绍 | 销售的秘密2| 《淫の方程式》动漫| 白毛女选段| 解语花电影完整版在线| 电视剧狂飙在线观看免费| 史宾机器人 重启| 送审样片| 银瓶梅全集在线观看免费| 小女花不弃 电视剧| 电视剧《装台》| 年轻的小婊5线观高清5中字电影| 《德国女子护卫队:满天星》在线观看| 強暴處女在线播放| 放逐粤语| 天降女子在线免费观看| 与凤行电视剧全40集免费观看| 满天星麦乐迪电影在线观看完整免费| 僧侣之夜在线观看动漫完整版| 神墓在线观看全集免费观看动漫| 梦精记下载| 美式保罗2经典版| 女朋友63中汉字三义角色介绍| 校内电影| 野花视频在线观看免费完整版1| 天乩全集免费观看完整版| 壮志凌云2(啄木鸟)| 猩球崛起4:新世界| 权力王朝:默多克家族电视剧| 枭起青壤免费观看| 湖南男子捅杀3人| 爱唯侦察9集免费观看| 九尾狐姐姐传电视剧| 他其实没那么喜欢你在线观看| 妈妈我想你高清日本免费视频| 《特殊游泳教练》| 《总统夫人》(罗丽·星克莱尔)| 一生一世电视剧完整版| 产妇大出血院长深夜跨院摇人| 马志明 大保镖| 朋友换麦子3:普通话版| 360天大佬第二季在线观看| 声生不息宝岛季免费观看完整版| 寒战2电影| 星际迷航:叛徒| 麦乐迪与父亲经典片段| 刀蓝火锋| 地爆天星无删减版免费观看 | 德昌县| 家庭老师| 妹妹学着点:打脸绿茶全集免费| 完整的恶魔之眼| 渴望城市演员表| 丰满的继牳3小早川怜子BD电影| 女超人麦乐迪电影无删减| 美景之屋免费观看全集电影完整版| 电视剧抗战奇侠| 变种女狼3归来| 日本战狼6免费完整版| 16岁大学生真人免费观看电视剧| 余罪第一季完整版| 杀破狼贪狼免费观看| 《亚当与夏娃》| 年轻的继拇3| 采桑子 叶广岑| 金装四大才子国语版| 旋风管家国语版| 斗罗大陆213集| 《乳头按摩》在线观看| 徐杰怒吼| 贝兹旅馆| 和你在一起乔任梁| 我爱你比你爱我多| 《妽妽的丰满大胸》hd| 万古齐天全集免费观看| 火影忍者剧场版失落之塔| 唐朝浪漫英雄剧情介绍| 人生特别档案| 美容院的特殊待遇在线| 电影《娜塔莉》在线观看完整版中文 | 美乃雀所有影片免费观看| 小妖怪的夏天动画片| 终极三国全集| overflwer全集免费播放| 免费观看甜蜜惩罚| 心动的信号7全集免费观看| 李卫高士奇| 《睫毛膏4》完整版| 美胸按摩电影| 万物生在线完整版免费观看| 满天星电影在线观看完整免费高清 | 韩国保险公司推销员8| 免费看办公室秘密全集 | 华人城小说| 小敏家在线电视剧免费观看全集 | 《胆小鬼》电视剧| 维生素爱香水| 电影长津湖战役完整版播放免费观看| 《女超人:麦乐迪》艾玛| 多名“将军”在洛阳高铁站霸气接站| 铁在烧 电视剧| 老电影武打片| 诱惑学校| 三妻四妾免费观看完整版高清,皇家女御厨17.3| 魔能圣甲| 无可奈何| 牧神记第39集在线观看视频| 法医秦明之读心者免费看| 天海翼女教师无删减版| 安东尼奥尼中国| 七月与安生迅雷下载| 神印王座免费完整观看| 善良的小峓子小火星在线观看| 《肉蔻之香法版》| 阵地电视剧免费观看完整版高清 | 叶咲ゆめ| 美丽小蜜蜂2美国版中文| 一人之下剧场版在线观看| 《比加拉什》免费观看| 大平调张三李四| 《部长出差的日子》电影 | 太上天尊短剧免费观看| 速度与激情10| 插曲的痛30集全集观看免费西瓜| 四位师奶按摩记| 甜性涩爱 百度百科| 甜蜜惩罚2在线观看| 高清《安娜情欲史》未删减版| 伸冤人3高清电影免费观看| 韩惠珍满天星三部曲免费阅读| 牧神记40集全免费播放| 天乱之战白蛇传| 丰满学生美女电影| 东成西就2011百度影音| 新世纪福音战士序| 麦乐迪完整版在线| 成龙历险记免费观看完整版| 甜蜜惩罚2我是看守专用宠物| 《夕颜》动漫番剧全集免费观看| 多人轮换| 年轻的女朋友在线观看 | 春子家有喜事了| 上虞市| 男女愁愁愁高清全集| 唐太宗李世民全集| 芭比之时尚奇迹中文| 终结者 2| 正者无敌电视剧| 印度空姐2全集观看完整版| 刘亦菲的乳房| 你是我的眼mv| 见好就收电视剧免费完整版| 法国版《小美人》| 埃及皇后3满天星版| 酒店1—100集免费观| 免费怀旧短剧| 银河奥特曼3| 金箍棒传奇2:沙僧的逆袭 电影| xl司令动漫第一季| 《迪拜的女孩2满天星》在线观看| 上海路炒粉事件| 大男人的小胶妻| 人猿泰山1995意大利哪里能看 | 吉安市| 勇往直前04春季篇| 索多玛120天迅雷下载| 黄海波江湖兄弟| (丰满的继牳3)理伦片在线观看 | 银瓶梅全集在线观看免费| 猩球崛起4新世界免费高清观看| 纸箱包子| 白鹿范丞丞合唱狂恋| 美姊妹剥赤板栗| 斗破苍穹第四季17集在线观看| 建党伟业观后感范文| 北京生物疫苗是国药吗| 水手服饲养未减版主演是谁| 《无憾》电影完整版| 叛狱无间| 电视剧《人世间》| 女人的天空全集免费观看| 江山市| 2008新金银5国语版全集演员表 | 《旷野之物》朴智炫| 公之浮之中字10的更新时间| 慈禧的生活秘史免费播放| 老电影武当| 私奔原唱| 妹汁全集在线观看国语| 私人家教在线观看| 萌探探探案免费观看西瓜影视| 变形金刚:超能勇士| 绝世网红韩剧| 中国远征军电视剧| 桓仁| 和部长一起出差旅的日子电影| 钢铁侠1国语版高清| 苍井空电影在线观看| 我的女婿太逆天电视剧| 打开你那茂密的小森林| 无憾坎贝奇| 偷情五日| 对不起我不爱你了短剧免费观看 | 电影无毒不丈夫国语版| 忽悠女老板| 死神来了5高清完整版在线观看| 邻居也疯狂免费观看电视剧| 美国xxxx69| 特聘外卖员| 武林风泰山| 张新伟遗书| 电影英雄无泪| 炸天小姐满天星观看| 迦楠大人的白给是恶魔级全集观看| 小姨子的诱惑 电影| 《牙医姐妹》1986版| 喜爱夜蒲2电影完整播放| 平凡的世界电视剧全集| 搞笑虫子| 《玛丽玛丽》满天星中文| 神雕侠侣电影版| 租借女友动漫| 迎风青春电视剧免费观看| 《满天星》在线观看| 美式禁忌1——7| 末路电视剧| 珠江人家一共多少集| 金花梅花2| 赫板丽| 迷惘的爱| 速度与激情10 电影| 圣域传说| 交通事故现场| 美丽小蜜蜂5大结局| 售楼小姐的秘密在线播放| 一个人免费看的高清电影在线观看 | 韩国小电影男子获得遥控器控制女领导 | 演员李桓| 《一分钟》电影免费观看高清| 兔子先生 天美影院| 销冠的秘密2| 普京访华随行人员都有谁?| 无耻之地电影美国完整版在线观看| 歌曲披着羊皮的狼| 都铎王朝第四季| 察哈| 麦乐迪女超人在线播放| 唐朝好男人电视剧40| 荒野求生贝尔 格里尔斯| 男神爱上:暗恋成真全集免费| 韩国电影十八禁顶楼的大象在线播放 | 凉宫春日2009| 韩剧《迷雾》在线观看| 木下凛凛子主演| 深夜的蠕动未增删有翻译日剧吧| 假面骑士REVICE第五集| 日本战狼12免费国语| 星级狂魔满天星原版| 《曼娜艳荡性史》| 冰冷热带鱼电影无删减完整版| 伽师县| 女超人(麦乐迪)版2013电影在线观看 | 瓜达卢佩的玫瑰电影免费观看| 打蛇电影完整版免费观看国语| 电车魔女| 失笑电视剧沈月在线观看| 年轻的嫂子2免费观看| 欧式少女16大结局全集 | 非卖品广播剧| 了凡四训1一23集连续剧免费观看| 3d肉浦团| 高校女教师在线观看| 《美容院:特殊待遇》在线| 毒舌大状粤语在线观看| 顺风妇产科高清| 人口第一大省首现负增长| 《莫莉特别的酒店》在线观看| 《借钱2》妈妈| 维修工的艳遇韩国电影 | 小猪佩奇全集大全| 海魂电视剧| 天际浩劫国语| 老千电影| 3对1初体4| 一起愁愁在线观看完整版| 战斗之魂:少年激霸弹| 大叔的爱泰剧一共多少集| 灵域电视剧免费观看全集在线| 莲花楼40集全| 电影《需要爸爸播种子》HD中字在线观看,高清完整版美国倫理片_屠夫电影网 | 美国式禁忌睫毛膏4完整版| 在线观看视频《瓜达卢佩的玫瑰》| 水浒传电视剧在线观看免费完整版| 九重紫电视剧免费播放| 普门品全文念诵及回向全文| 《10号舱房的女人》| 公的浮之手中字舅舅| 美乃雀电影在线| 深海狂鲨3| 大炕上泄欲老女人| 《芈月传》| 明明不喜欢 在钱| 雪中悍刀行第2部免费观看| 水手服饲养未减版主演是谁| 法国满天星《女飞行员》| 语义错误第二季生肉| 女帝江山:美男环绕完整版观看| 金金丁字广场舞私人订制| 温柔的背后吻戏| 经纪人回应张雪峰被强制送医| 《女律师的坠落》大结局剧情解析 | 怒火十二小时电影免费播放| 《美丽屋景1| 韩国电影售楼秘密| 周杰伦摩天轮演唱会官方完整版| 玉浦团之| 国足vs澳大利亚直播在线观看|