【自學AI#2】ANN入門與Arduino Nano案例教學

作者:賴桑

接續第一集介紹了 AI 的歷史脈絡,本篇將來教大家什麼是 NN ANN ?同時還會使用Arduino Nano 跑一個 ANN 小案例,手把手帶你把PC做出 ANN 的成果,轉移到 Arduino Nano 去執行。

到底有多少 NN 

上一篇文提到神經網路 NN ,根據目前常見的應用,大概有幾個是常見的:「 ANN kNN CNN  RNN R-CNN 。」那這些 NN 又是什麼?怎麼用呢?其實,每一種 NN 會根據其設計上的特性跟用途,而有適用的範圍,其實就跟醫師會對症下藥的道理一樣。因此該採用哪種做哪個功能,最簡單的方法,就是直接試試看。

最老式結構:ANN

ANN 的全名叫做 Artificial neural networks ,有些教科書稱 connectionist systems ,事實上還是那句老話:模仿動物的腦結構,ANN的構型就類似這張圖:

ANN 的構型(圖片來源:賴桑提供)

Input 、 Output 顧名思義就是輸入與輸出部份,而  Hidden 就是在實際進行運算的部分,這部分根據之前的文章便能看懂,實際上就是一堆數據,不然就是一堆函數; Hidden 到底可以有幾個?以上次的CNN 為例,CNN能分好幾層,每一層還很多個。

其實根本沒有固定答案,不管 Hidden 分幾層、每層要幾個,這兩項都只有四個大字「適量就好」可以描述。為什麼?根據上圖,每一個都是個數值或是函數,這是不是每一個 Hidden 的神經元都需要記憶體空間?那越多則記憶體消耗空間越多。另外,分那麼多層,一層算過又推到下一層,那等於執行所需要的步驟越多,執行步驟越多,跑起來應該是越慢吧?

不論哪一種 NN Hidden 層的部分,越小、越少那表示對電腦的資源需求越低,這也就是為什麼現在連一些微控制器 Micro-controller 都可以做到以往在 PC 上才能跑的 NN 所達到的效果。可是話又說回來, ANN 到底可以做什麼?以下就來跑個小案例看看。

ANN 案例實作

上一篇提到麻省理工學院明斯基教授,曾經發表一篇報導,提到所謂 Perceptron 連組合邏輯數位電路 XOR 都辦不到,那我們就來讓一個八位元微控制器來試試,打臉明斯基教授一下。

首先,可能有些大大不是電機電子背景的,其實關於 XOR呢 ,數位電路的邏輯運算; XOR 的全名叫做「互斥或」 exclusive or ,它的意思就是:當兩個為一樣的話,就輸出 0 ,它的真值表 Truth table會像這樣:

輸入A 輸入B 輸出
0 0 0
0 1 1
1 0 1
1 1 0

所以,我們就來發揮一下 Maker 精神,拿 Arduino Nano 這種小開發板,上面只有 8 bits 微控制器的來試試,可是不少大大就立刻反應了:辦得到嗎?山不轉路轉,仔細想想,那些 Hidden 層的各個神經元,有規定一定要放在同一台電腦,或者同一個微控制器嗎?並沒有吧。

那既然如此, PC 來做出 ANN 的成果,再把成果轉移到 Arduino Nano 去執行,不就好了嗎?我們先用 Python 寫 PC 上可以執行的程式, PC 上的程式執行 ANN 的訓練成果,再把訓練成果利用 Arduino IDE 搬到 Arduino Nano 上執行。首先,這得讓大家動動手,先參考怎麼安裝 NumPy 這個 package ,直接在命令提示字元的視窗裡輸入 pip install numpy 就會搞定, PC 上的程式原始碼是這樣:

import numpy as np


# Times to run
epoch = 10000


# There are 2 inputs
inputLayerSize = 2


# NN nodes
hiddenLayerSize = 3


# Only one output
outputLayerSize = 1


L=0.1


# There are 2 inputs for XOR
X = np.array( [ [0,0], [0,1], [1,0], [1,1] ] )


# The truth table of XOR
# ANN just can learn from truly examples!!!
#     (adjust the weight and bias to make output is getting close to target by input)
Y = np.array( [ [0], [1], [1], [0] ] )


def sigmod(x):
    return 1 / (1 + np.exp(-x))


def sigmoid_deriv(x):
	return x * (1 - x)
	
Wh = np.random.uniform(size=(inputLayerSize, hiddenLayerSize))
Wz = np.random.uniform(size=(hiddenLayerSize, outputLayerSize))


for i in range(epoch):
  H = sigmod(np.dot(X, Wh))
  Z = np.dot(H, Wz)
  E = Y - Z
  dZ = E * L
  Wz += H.T.dot(dZ)
  dH = dZ.dot(Wz.T) * sigmoid_deriv(H)
  Wh +=  X.T.dot(dH)


print("**************** error ****************") 
print(E)
print("***************** output **************") 
print(Z)   
print("*************** weights ***************") 
print("input to hidden layer weights: ")     
print(Wh)
print("hidden to output layer weights: ")
print(Wz)

如果不是 Copy Paste 的大大,應該看懂了吧?這程式沒多大,說穿了  X = np.array ([ [0,0], [0,1], [1,0], [1,1] ] )這一列就是在描述上面那個真值表,有關A、B兩個輸入。而 Y = np.array ([ [0], [1], [1], [0] ] ) 這一列就是真值表裏面的輸出一欄,那 hiddenLayerSize = 3 就是 Hidden 層裡面含有三個神經元,當然你可以自己改看看。

整個程式跑起來,就是根據這些條件,用已知的 Input 對 Output 去─湊答案,把答案湊到可以接受的情況後,就是 Hidden 層的結果(訓練的成果,也就是很多期刊文獻寫的規則 Rules );總之,記得所謂深度學習的程式設計,跟以往我們寫程式的不同在於:

用已知的 Input 對 Output 湊答案,把答案湊到可以接受的情況後,就是 Hidden 層的結果(圖片來源:賴桑提供)

這程式執行起來會出現這樣的結果,也就是等等要轉給 Arduino Nano 的數據:

程式執行結果(圖片來源:賴桑提供)

接著, Arduino Nano 只要針對規則的部分執行就好!這就是 Arduino 部分的程式檔案:

#include <math.h>

#define B1  4
#define B2  7
#define LED 13

#define PERCEPTRON  3

int X[1][2]  = { {1,0} };

/*these matrices was calculated by python */
float W1[2][PERCEPTRON] =   {
                    {0.38492545, 3.94078829, 4.24865286}, 
                    {3.83936678, 0.20450267, 4.27767947}
                   };
float W2[PERCEPTRON][1] =   { {-2.6730679}, 
                     {-2.3817902}, 
                    {4.99647104} };
float Wo1[1][PERCEPTRON];
float sum = 0;
float Y = 0;

/*sigmoid function*/
float sigmoid (float x)
{
    return 1/(1 + exp(-x));
}

void setup()
{
  Serial.begin(115200);

  pinMode(B1, INPUT); 
  pinMode(B2, INPUT); 
  pinMode(LED, OUTPUT);

  digitalWrite(LED, LOW);
}

void loop()
{
  X[0][0] = digitalRead(B1);
  X[0][1] = digitalRead(B2);
  Serial.print("Button 1:"); Serial.print(X[0][0]);
  Serial.print("\tButton 2:"); Serial.println(X[0][1]);

  /* calculate forward part based on weights */
  //hidden layer
  for(int i=0; i<1; i++)
  {
      for(int j=0;j <PERCEPTRON; j++)
      {
          for(int k=0; k<2; k++)
          {
              sum += X[i][k]*W1[k][j];
          }
          Wo1[i][j] = sigmoid(sum);
          sum = 0;  
      }
  }
  //output layer
  for(int i=0; i<1; i++)
  {
      for(int j=0;j <1; j++)
      {
          for(int k=0; k<PERCEPTRON; k++)
          {
              Y += Wo1[i][k]*W2[k][j];
          } 
      }
  }

  Serial.println(Y, 2);

  Y = round(Y);
  if (int(Y) == 1)
    Serial.println("---- Should be... ON ----");
  else
    Serial.println("---- Should be... OFF ----");
  digitalWrite(LED, int(Y));
  Y = 0;

  delay(2000);
}
<!--stackedit_data:
eyJoaXN0b3J5IjpbLTk3MDAwMzY4M119
-->

另外,要先把剛剛 Python 執行的成果複製給 Arduino Nano ,像是這樣:

記得把Python 執行的成果複製給 Arduino Nano(圖片來源:賴桑提供)

之後在 Pin 4、7 各透過 10k 固定電阻,各外掛一個按鈕,就可以編譯後試試看,詳細可以看這影片:

電機系的大大們一定說:「用  TTL 就好啦!」,發揮一下想像力,如果說拿來做預測呢?我一直跑 ANN 訓練出規則,固定以後透過改變輸入的項目內容,那輸出…就應該會跟著變動囉?這樣不就可以預測有或沒有的效果了嗎?

像是這篇「 PM2.5 Forecasting Based on Artificial Neural Network and Genetic Algorithm 」期刊,就是利用 ANN 當作基礎,去預測 PM2.5 的汙染: https://pdfs.semanticscholar.org/9a9e/838619bd4fecb882aade0c96a84dc59a2c6d.pdf

利用 ANN 當作基礎,預測 PM2.5 的汙染的期刊(圖片來源:賴桑提供)

小結

這一集我們介紹了最老式的一種結構 ─ ANN,很明顯地這結構最大特性就是:只會學習已經固定的規則,其實所謂學習,就是給 ANN 已經知道的輸入跟輸出,讓 ANN 去湊答案而已。

但也因為這樣的特性,我們發現其實只要動點小手腳, ANN 就可以變成預測結果是有用或無用的計算系統。之後的章節,我們將逐次把多種 NN 抓出來,一一舉例讓大家試試,歡迎大家一起,自己寫些小的程式來驗證。

(責任編輯:楊子嫻)

賴建宏

Author: 賴建宏

社群稱號為「賴桑」的他,以電子電機的背景,熱衷於OSHW的應用開發與實作。取得台北科技大學電子所博士學位,目前主推「農林漁牧大業」計畫的迷你型魚菜共生系統開發。

Share This Post On

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *