MLP多層感知器

      在〈MLP多層感知器〉中尚無留言

神經網路(Neural Network)

神經網路至少包含了二個層,即輸入層跟輸出層。

二層感知器

二層感知器中,一個輸入層,另一個為輸出層。比如 28*28的Mnist圖片,展開後就是784個像素。所以輸入層就有784個神經元,輸出層則有10個神經元。如下圖

多層感知器(MLP) Multilayer Perceptron

多層感知機是一種前向傳遞類神經網路,在輸入及輸出層外,中間多了至少一個隱藏層。也就是說包含了至少三層結構(輸入層、隱藏層和輸出層),並且利用到倒傳遞技術達到學習(model learning)的監督式學習。如下圖所示。

MLP預測手寫數字

底下代碼,實現手寫數字預測,比先前的模型更加準確。

不過在開始預測前,準備好的圖片最好為黑底白字,因為每個字的每個點~~都代表一個 “亮點”,黑色的神經元其值為 0,非黑色的部份其值介>0並且 <=1。然後由這些點計算出迴歸線,所以就有a及b二個參數(y=ax+b)。回歸線就是把二維的資料減少成一條線來表示。

MLP 模型

摸型中先使用Flatten層將28*28二維陣列平化展開成一維具784個元素(特徵點)的陣列。輸出使用softmax()函數,將結果分散在0~9之間10個數字。

訓練時,y_pred=model(train_data_sigmoid),其實就是調用 class Model 的 __call__()方法。那麼我們不就應該覆寫 __call__()嗎,怎麼覆寫 call()?? 因為 __call__()會先作一些事情,然後再調用 call()。所以我們只需覆寫 call() 即可。

請在專案下新增 MLP.py 檔,然後輸入如下代碼。

import tensorflow as tf
import keras
from keras.src.layers import Dense, Flatten

class MLP(keras.Model):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.flatten=Flatten()#將第一維(筆數)以外的維度展平成一維
        self.dense1 = Dense(units=500 , activation=tf.nn.relu)#資料由784會變成500個
        self.dense2 = Dense(units=400, activation=tf.nn.relu)
        self.dense3 = Dense(units=300, activation=tf.nn.relu)
        self.dense4 = Dense(units=200, activation=tf.nn.relu)
        self.dense5 = Dense(units=100, activation=tf.nn.relu)
        self.dense6 = Dense(units=10, activation=tf.nn.softmax)
    def call(self, inputs): #[60000,28,28]
        x=self.flatten(inputs) #[60000,784]
        x = self.dense1(x) #[60000,500]
        x = self.dense2(x) # [60000,400]
        x = self.dense3(x) # [60000,300]
        x = self.dense4(x) # [60000,200]
        x = self.dense5(x) # [60000,100]
        x = self.dense6(x) # [60000,10]
        return x

模型訓練

tf.keras.losses.sparse_categorical_crossentropy稱為交叉熵,可計算其中的值離散程度。值愈大,離散愈大。是以log函數作為統計計算。

模型訓練完整代碼如下。

import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
import tensorflow as tf
import numpy as np
import keras
from MLP import MLP
learning_rate=0.001
model=MLP()
mnist = tf.keras.datasets.mnist
(train_data, train_label), (test_data, test_label) = mnist.load_data()
optimizer=keras.optimizers.Adam(learning_rate=learning_rate)

#要圖形像素顏色由 0~255 歸一化成 0~1, 否則每次的損失函數值會不一樣
train_data_sigmoid = train_data.astype(np.float32) / 255.0
for i in range(200):#逼近200次, 也就是在訓練模習
    with tf.GradientTape() as f:
        y_pred=model(train_data_sigmoid)
        #crossentropy : 交叉熵
        loss=keras.losses.sparse_categorical_crossentropy(y_true=train_label, y_pred=y_pred)
        loss=tf.reduce_mean(loss)
        print(f"第{i+1:3d}次逼進: loss:{loss.numpy()}")
    y_grad=f.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(y_grad, model.variables))
model.save("mlp.keras")

預測

model.predict(new_img) 測出的數字為 n*10的陣列,如[0.01,0.06,0.99,0,0,0,0,0,0,0,0],裏面的數值是每個數的可能機率,所以要找最大值,使用np.argmax(new_pred, axis=-1) 將之轉換成 2。

優化器選用Adam,相關資訊請參考優化器使用

預測完整代碼如下

import numpy as np
import tensorflow as tf
from keras.src.saving import load_model
import pylab as plt
from MLP import MLP

model=load_model(
    "mlp.keras",
    custom_objects={"MLP":MLP}
)

mnist=tf.keras.datasets.mnist
(train_data, train_labels), (test_data, test_labels) = mnist.load_data()

test_data_sigmoid=test_data.astype(np.float32)/255.0
accuracy = tf.keras.metrics.SparseCategoricalAccuracy()#精準度測量器
predict=model.predict(test_data_sigmoid)
label=np.argmax(predict, axis=-1)#轉成實際的數字
fig=plt.figure(figsize=(12,6))
for i in range(100):
    ax=fig.add_subplot(5,20,i+1)
    ax.imshow(test_data[i], cmap='gray')
    ax.set_title(label[i])
    ax.axis("off")

# y_true : 是實際的數字[7 2 1 ...4 5 6]
# y_pred : 是預測的機率值 
# [
# [0.000e+00 0.000e+00 9.992e-01 8.000e-04 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00 0.000e+00], 
# [0.000e+00 0.000e+00 0.000e+00 5.000e-04 0.000e+00 9.157e-01 8.380e-02 0.000e+00 0.000e+00 0.000e+00], 
#  ......
# ]
accuracy.update_state(y_true=label, y_pred=predict)
print(f'精準度 : {accuracy.result()*100:.2f}%')
plt.show()

隱藏層

隱藏層愈多愈精準,但會產生過渡擬合,所以需要加池化層。

Model 隱藏層愈多,GPU的效能就會突顯出來,所以GPU所耗資源就會變高。

相對的,如果 epoch 愈多,因為是使用 Python 的迴圈運作,那麼使用 CPU 的效能會高一點,當然CPU所耗資源也更高。

自定義層 — todo

如果要自定義層,可以繼承tf.keras.layers.Layer,然後覆寫 __init__, build及call三個方法,整個架構如下

import tensorflow as tf
class LinearLayer(tf.keras.layer.Layer):
def __init__(self, units):
super().__init__()
self.units=units
def build(self, input_shape):
self.w = self.add_variable(name='w',shape=[input_shape[-1], self.units], initializer=tf.zeros_initializer())
self.b = self.add_variable(name='b',shape=[self.units], initializer=tf.zeros_initializer())
def call(self, inputs):
y_pred = tf.matmul(inputs, self.w) + self.b
return y_pred
class LinearModel(tf.keras.Model):
def __init__(self):
super().__init__()
self.layer = LinearLayer(units=1)
def call(self, inputs):
output = self.layer(inputs)
return output

參數調整

常聽人說,模型有許多參數要調整。當我們自已建立模型,要改的東西就變的很有彈性,比如
1. 演算層,加入池化層
2. 損失函數 (Adam/SGD…)
3. learning_rate
4. epoch

發佈留言

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