神經網絡極簡入門

神經網絡是深度學習的基礎,正是深度學習的興起,讓停滯不前的人工智能再一次的取得飛速的發展。

其實神經網絡的理論由來已久,靈感來自仿生智能計算,只是以前限於硬件的計算能力,沒有突出的表現,
直至谷歌的AlphaGO的出現,才讓大家再次看到神經網絡相較於傳統機器學習的優異表現。

本文主要介紹神經網絡中的重要基礎概念,然後基於這些概念手工實現一個簡單的神經網絡。
希望通過理論結合實踐的方式讓大家更容易的理解神經網絡。

1. 神經網絡是什麼

神經網絡就像人腦一樣,整體看上去非常複雜,但是其基礎組成部分並不複雜。
其組成部分中最重要的就是神經元neural),sigmod函數layer)。

1.1. 神經元

神經元(neural)是神經網絡最基本的元素,一個神經元包含3個部分:

  • 獲取輸入:獲取多個輸入的數據
  • 數學處理:對輸入的數據進行數學計算
  • 產生輸出:計算後多個輸入數據變成一個輸出數據

image.png
從上圖中可以看出,神經元中的處理有2個步驟。
第一個步驟:從藍色框變成紅色框,是對輸入的數據進行加權計算後合併爲一個值(N)。
\(N = x_1w_1 + x_2w_2\) 其中,\(w_1,w_2\)分別是輸入數據\(x_1,x_2\)的權重。
一般在計算\(N\)的過程中,除了權重,還會加上一個偏移參數\(b\),最終得到:
\(N = x_1w_1 + x_2w_2+b\)

第二個步驟:從紅色框變成綠色框,通過sigmoid函數是對N進一步加工得到的神經元的最終輸出(M)。

1.2. sigmoid函數

sigmoid函數也被稱爲S函數,因爲的形狀類似S形
image.png
它是神經元中的重要函數,能夠將輸入數據的值映射到\((0,1)\)之間。
最常用的sigmoid函數是 \(f(x)=\frac{1}{1+e^{-x}}\),當然,不是隻有這一種sigmoid函數。

至此,神經元通過兩個步驟,就把輸入的多個數據,轉換爲一個\((0,1)\)之間的值。

1.3. 層

多個神經元可以組合成一層,一個神經網絡一般包含一個輸入層和一個輸出層,以及多個隱藏層。
image.png
比如上圖中,有2個隱藏層,每個隱藏層中分別有4個和2個神經元。
實際的神經網絡中,隱藏層數量和其中的神經元數量都是不固定的,根據模型實際的效果來進行調整。

1.4. 網絡

通過神經元和層的組合就構成了一個網絡,神經網絡的名稱由此而來。
神經網絡可大可小,可簡單可複雜,不過,太過簡單的神經網絡模型效果一般不會太好。

因爲一隻果蠅就有10萬個神經元,而人類的大腦則有大約1000億個神經元,
這就是爲什麼訓練一個可用的神經網絡模型需要龐大的算力,這也是爲什麼神經網絡的理論1943年就提出了,
但是基於深度學習的AlphaGO卻誕生於2015年

2. 實現一個神經網絡

瞭解上面的基本概念只能形成一個感性的認知。
下面通過自己動手實現一個最簡單的神經網絡,來進一步認識神經元sigmoid函數以及隱藏層是如何發揮作用的。

2.1. 準備數據

數據使用sklearn庫中經典的鳶尾花數據集,這個數據集中有3個分類的鳶尾花,每個分類50條數據。
爲了簡化,只取其中前100條數據來使用,也就是取2個分類的鳶尾花數據。

from sklearn.datasets import load_iris

ds = load_iris(as_frame=True, return_X_y=True)
data = ds[0].iloc[:100]
target = ds[1][:100]

print(data)
print(target)

image.png
變量data100條數據,每條數據包含4個屬性,分別是花萼的寬度和長度,花瓣的寬度和長度。

image.png
變量target中也是100條數據,只有0和1兩種值,表示兩種不同種類的鳶尾花。

2.2. 實現神經元

準備好了數據,下面開始逐步實現一個簡單的神經網絡。
首先,實現最基本的單元--神經元
本文第一節中已經介紹了神經元中主要的2個步驟,分別計算出\(N\)\(M\)
image.png
計算\(N\)時,依據每個輸入元素的權重(\(w_1,w_2\))和整體的偏移\(b\)
計算\(M\)時,通過sigmoid函數。

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

@dataclass
class Neuron:
    weights: list[float] = field(default_factory=lambda: [])
    bias: float = 0.0
    N: float = 0.0
    M: float = 0.0

    def compute(self, inputs):
        self.N = np.dot(self.weights, inputs) + self.bias
        self.M = sigmoid(self.N)
        return self.M

上面的代碼中,Neuron類表示神經元,這個類有4個屬性:
其中屬性weightsbias是計算\(N\)時的權重和偏移;
屬性NM分別是神經元中兩步計算的結果。

Neuron類的compute方法根據輸入的數據計算神經元的輸出。

2.3. 實現神經網絡

神經元實現之後,下面就是構建神經網絡。
我們的輸入數據是帶有4個屬性(花萼的寬度和長度,花瓣的寬度和長度)的鳶尾花數據,
所以神經網絡的輸入層有4個值(\(x_1,x_2,x_3,x_4\))。

爲了簡單起見,我們的神經網絡只構建一個隱藏層,其中包含3個神經元
最後就是輸出層,輸出層最後輸出一個值,表示鳶尾花的種類。

由此形成的簡單神經網絡如下圖所示:
image.png

實現的代碼:

@dataclass
class MyNeuronNetwork:
    HL1: Neuron = field(init=False)
    HL2: Neuron = field(init=False)
    HL3: Neuron = field(init=False)
    O1: Neuron = field(init=False)

    def __post_init__(self):
        self.HL1 = Neuron()
        self.HL1.weights = np.random.dirichlet(np.ones(4))
        self.HL1.bias = np.random.normal()

        self.HL2 = Neuron()
        self.HL2.weights = np.random.dirichlet(np.ones(4))
        self.HL2.bias = np.random.normal()

        self.HL3 = Neuron()
        self.HL3.weights = np.random.dirichlet(np.ones(4))
        self.HL3.bias = np.random.normal()

        self.O1 = Neuron()
        self.O1.weights = np.random.dirichlet(np.ones(3))
        self.O1.bias = np.random.normal()

    def compute(self, inputs):
        m1 = self.HL1.compute(inputs)
        m2 = self.HL2.compute(inputs)
        m3 = self.HL3.compute(inputs)

        output = self.O1.compute([m1, m2, m3])
        return output

MyNeuronNetwork類是自定義的神經網絡,其中的屬性是4個神經元
HL1HL2HL3隱藏層的3個神經元;O1輸出層的神經元。

__post__init__函數是爲了初始化各個神經元。
因爲輸入層是4個屬性(\(x_1,x_2,x_3,x_4\)),所以神經元HL1HL2HL3weights初始化爲4個隨機數組成的列表,
偏移(bias)用一個隨時數來初始化。

對於神經元O1,它的輸入是隱藏層的3個神經元,所以它的weights初始化爲3個隨機數組成的列表,
偏移(bias)還是用一個隨時數來初始化。

最後還有一個compute函數,這個函數描述的就是整個神經網絡的計算過程。
首先,根據輸入層(\(x_1,x_2,x_3,x_4\))的數據計算隱藏層的神經元(HL1HL2HL3);
然後,以隱藏層的神經元(HL1HL2HL3)的輸出作爲輸出層的神經元(O1)的輸入,並將O1的計算結果作爲整個神經網絡的輸出。

2.4. 訓練模型

上面的神經網絡中各個神經元的中的參數(主要是weightsbias)都是隨機生成的,
所以直接使用這個神經網絡,效果一定不會很好。
所以,我們需要給神經網絡(MyNeuronNetwork類)加一個訓練函數,用來訓練神經網絡中各個神經元的參數(也就是個各個神經元中的weightsbias)。

@dataclass
class MyNeuronNetwork:
    # 略...

    def train(self, data: pd.DataFrame, target: pd.Series):
        ## 使用 隨機梯度下降算法來訓練
        pass

上面的train函數有兩個參數data(訓練數據)和target(訓練數據的標籤)。
我們使用隨機梯度下降算法來訓練模型的參數。
這裏略去了具體的代碼,完整的代碼可以在文章的末尾下載。

此外,再實現一個預測函數predict,傳入測試數據集,
然後用我們訓練好的神經網絡模型來預測測試數據集的標籤。

@dataclass
class MyNeuronNetwork:
    # 略...
    
    def predict(self, data: pd.DataFrame):

        results = []
        for idx, row in enumerate(data.values):
            pred = self.compute(row)
            results.append(round(pred))

        return results

2.5. 驗證模型效果

最後就是驗證模型的效果。

def main():
    # 加載數據
    ds = load_iris(as_frame=True, return_X_y=True)

    # 只用前100條數據
    data = ds[0].iloc[:100]
    target = ds[1][:100]

    # 劃分訓練數據,測試數據
    # test_size=0.2 表示80%作爲訓練數據,20%作爲測試數據
    X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

    # 創建神經網絡
    nn = MyNeuronNetwork()

    # 用訓練數據集來訓練模型
    nn.train(X_train, y_train)

    # 檢驗模型
    # 用訓練過的模型來預測測試數據的標籤
    results = nn.predict(X_test)
    df = pd.DataFrame()
    df["預測值"] = results
    df["實際值"] = y_test.values
    print(df)

運行結果可以看出,模型的效果還不錯,20條測試數據的預測結果都正確。
image.png

3. 總結

本文中的的神經網絡示例是爲了介紹神經網絡的一些基本概念,所以對神經網絡做了儘可能的簡化,爲了方便去手工實現。

而實際環境中的神經網絡,不僅神經元的個數,隱藏層的數量極其龐大,而且其計算和訓練的方式也很複雜,手工去實現不太可能,
一般會藉助TensorFlowKerasPyTorch等等知名的python深度學習庫來幫助我們實現。

4. 代碼下載

代碼量不大,總共也就200行不到,感興趣的話可以下載後運行試試。
simple_nn.zip: https://url11.ctfile.com/f/45455611-1242350800-e67991?p=6872 (訪問密碼: 6872)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章