CNN原理

      在〈CNN原理〉中尚無留言

神經網路(Neural Network)

神經元是人類腦部儲存資訊的基本功能單元,裏面又分為細胞体,樹突(接收体),軸突(傳遞功能)。所以一個神經元可儲存一個單位的資料。人類的神經元約有860億個(86G)。不過每個神經元可以儲存幾個byte, 應該還沒研究出來。假設每個神經元可以儲存1000byte, 那麼人類應該有86G*1000=86T byte的容量(嗯,86顆1T的硬碟)。只是好玩,粗步假設一下而以,別當真啦,本人覺的應該不止這個數字。

假設要辨識一張圖片,則每一個像素都是一個特徵,也就是說,每一個像素都是一個神經元。但一張圖的像素這麼多,如何如何有效的減少這些像素數量,形成第二層神經層,就是目前的工作。

神經網路由三個部分組成,分別是輸入層(Input Layers)、隱藏層(Hidden Layers)以及輸出層 (Output Layers)。

輸入層

就是輸入資料(Data)的特徵值 (Features),舉個例來說,今天要預測一個人是男生還是女生,可以依照這個人的身高、體重、髮長等特徵來判斷,將這些特徵輸入,就是輸入層的工作。

隱藏層

隱藏層就是進行運算的工作,由神經元(Neuron)組成,透過前向傳播(Forward propagation) 計算出我們的輸出值。

輸出層

而輸出層為經由隱藏層運算後所得到的預測值,並經由縮小預測值與實際值的差異,然後更新隱藏層的參數,最後訓練出一組權重(weights)

CNN

Convolutional Neural Network,卷積神經網路,好一個奇怪的名詞。意思是找一個長寬固定的過濾片(如3*3),然後與圖片上所有的點作內積。這個Filter又稱為kernel。捲積圖解如下

第一次捲積

第二次捲積

第三次捲積

第四次捲積

第五次捲積

最後一次捲積

所以原本資料有 8 * 8 = 64個值,捲積 strides = 2 時,就會產生 4 * 4 = 16個值。

通常,Filter是一大堆。假設有100個Filter, 則就會產生 4*4*100=1600個值。

CNN架構 : Convolution->Max Pooling->Convolution->MaxPooling_>Flatten->全連接層
Filter : 看全連接輸出層有幾個值,就有幾個filter,而Filter裏面的值,就是學習出來的。也就是我們訓練的目的。

CNN輸入層

可以使用均值化,歸一化, PCA等方式。如下原始資料散佈在右邊。均值化是要把中心點移到(0,0)的位置。然後再把扁長的形狀歸一化,把長及寬各自限制在 -1~1之間

 

CNN隱藏層

CNN隱藏層又分為卷積層,活化層,池化層

卷積層

卷積層為計算卷積的工作。首先將圖片的二維陣列資料一維化,一維化的資料稱為神經元。然後使用kernel卷積成一次卷積神經元。一維卷積亦可再次二次卷積,三次卷積…。最後產生全連接層。

kernel是的維度可以為3*3,5*5,7*7。最好是奇數格,因為奇數才有中心點至於kernel裏的值是多少,這就是模型要訓練的目的了。

 

活化層

活化層常用幾個函數,如Sigmod(S函數),Tanh, ReLU, Leaky ReLU…..。為什麼要有活化層呢?? 因為經過上述卷積後,有可能某個元素的值變成了負值,也有可超出範圍值。但在圖形裏,像素的顏色值是介於0~255之間,不能有負值,也不能超出255。所以如何把負值給去除掉,就有上述的方法。如下代碼,就會產生負值。甚於是大於255以上。

from MahalCv import MahalCv as cv
import cv2
import numpy as np
img=cv.read('p4.jpg')
img=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img=cv.resize(img, scale=0.2)
h, w=img.shape
img=img.reshape(w*h)
k=np.array([[-1,0,1], [-1,0,0], [0,0,1]])
img=np.convolve(img, k.reshape(9), 'same')
img=img.reshape(h, w)
print(img)
結果 :
[[-230 -224 -220 ... -51 -1 -52]
[ -49 -56 -52 ... -50 1 -49]
[ -51 -53 -56 ... -54 -1 -50]
...
[ 19 20 17 ... 15 2 15]
[ 13 20 16 ... 14 -2 13]
[ 11 16 17 ... 77 1 80]]

如何把超出範圍的值挑出,又如何安排後續的值。下面圖形描述其方式

 

池化層

經過卷積及活化後所產生的特徵神經元,有些是多餘的資訊,此時就用池化層(pooling)將之去除掉。我們下採樣就是為了去掉這些冗餘信息,所以並不會損壞識別結果。去除多餘的資訊常用的有二種: Max pooling及Average pooling

Max pooling : 設定如 2*2格的窗口,然後在是四格之中,取最大的值,其它值就丟掉。
Average pooling : 比如以 2*2格的窗口,取這四格的平均值

CNN全連接層

通常全連接層在卷積神經網絡尾部。當前面卷積層抓取到足以用來識別圖片的特徵後,接下來的就是如何進行分類。 通常會將卷積網絡的末端得到的長方體平攤成一個一維的向量,並送入全連接層配合輸出層進行分類。比如手寫數字,總共有0~9共10個數字,所以卷積神經網絡的輸出層就會有10個神經元。

手寫數字預測

底下使用CNN預測手寫數字,可達到100%。底下的CNN第一層,因為strides為2,每次卷積步進為2, 所以就會由28*28變成14*14矩陣。第二次卷積,就會又變成7*7矩陣。

其實下面程式,經測試,只需卷積一次即可。不過扁平化需改成
self.flatten = tf.keras.layers.Reshape(target_shape=(14 * 14 * 32,))。

另外batch_size除了要看顯卡的記憶体大小外,也影響了逼近的次數。batch_size愈小,逼近的次數也會多,精準度也愈高。

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
import numpy as np
import cv2
import pylab as plt
cpus = tf.config.list_physical_devices (device_type='CPU')
#tf.config.set_visible_devices (devices=cpus)
class MNISTLoader():
    def __init__(self):
        mnist = tf.keras.datasets.mnist
        (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
        # MNIST中的圖片預設為uint8(0-255的數字)。以下程式碼將其正規化到0-1之間的浮點數
        self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1)# [60000, 28, 28, 1]
        self.train_label = self.train_label.astype(np.int32)# [60000]
        self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]
    def get_data(self, batch_size, index):
        s=index*batch_size
        e=(index+1)*batch_size
        return self.train_data[s:e], self.train_label[s:e]
class CNN(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.conv1 = tf.keras.layers.Conv2D(
            filters=32,          # 卷積層神經元(卷積核)數目
            kernel_size=[5, 5],  # 接受區的大小
            padding='same',      # 大小寫敏感度(vaild 或 same)
            activation=tf.nn.relu# 激活函数
        )#共 14*14*32 = 6272個神經元
        self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
        self.conv2 = tf.keras.layers.Conv2D(
            filters=64,
            kernel_size=[5, 5],
            padding='same',
            activation=tf.nn.relu
        )#共7*7*64 = 3136個神經元,是第一次捲積的一半
        self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
        self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))
        self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
        self.dense2 = tf.keras.layers.Dense(units=10)

    def call(self, inputs):
        x = self.conv1(inputs)# [batch_size, 28, 28, 32]
        x = self.pool1(x)     # [batch_size, 14, 14, 32]
        x = self.conv2(x)     # [batch_size, 14, 14, 64]
        x = self.pool2(x)     # [batch_size, 7, 7, 64]
        x = self.flatten(x)   # [batch_size, 7 * 7 * 64]
        x = self.dense1(x)    # [batch_size, 1024]
        x = self.dense2(x)    # [batch_size, 10]
        output = tf.nn.softmax(x)
        return output
#batch_size 每次訓練的資料量不能太大,不然GPU記憶体不足(超過2000個,視顯卡內存而定)
#且batch_size,造成num_batches太小,逼進次數太少而loss值太大
learning_rate = 0.001
batch_size = 500
model=CNN()
data_loader = MNISTLoader()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
num_batches = int(data_loader.num_train_data // batch_size)
for i in range(num_batches):
    with tf.GradientTape() as tape:
        X, y = data_loader.get_data(batch_size, i)
        y_pred = model(X)
        loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
        loss = tf.reduce_mean(loss)
        print("第%3d次逼進: loss %f" % (i+1, loss.numpy()))
    grads = tape.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

imgs=[]
for i in range(10):
    img=cv2.imdecode(np.fromfile(f'{i}.jpg', dtype=np.uint8), cv2.IMREAD_UNCHANGED)
    img=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img=cv2.resize(img, (28,28), interpolation=cv2.INTER_LINEAR)
    imgs.append(img)

new_img=np.array(imgs)#預測資料的圖形
new_img=np.expand_dims(new_img.astype(np.float32) / 255.0, axis=-1)
new_true=np.array([0,1,2,3,4,5,6,7,8,9])#預測資料的標籤

sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()#信心度測試器
new_pred=model.predict(new_img)#[[1,0,0,....0], [0,1,0,0,.....0]......]
new_target=np.argmax(new_pred, axis=-1)#轉成實際的數字
sparse_categorical_accuracy.update_state(y_true=new_true, y_pred=new_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())

for i in range (10):
    axes = plt.subplot(1, 10, i + 1)
    axes.set_xticks([])
    axes.set_yticks([])
    axes.set_title(new_target[i], fontproperties='Simsun')
    axes.imshow(new_img[i], cmap='gray')
plt.show()
結果:
第117次逼進 : loss 0.031104
第118次逼進 : loss 0.032790
第119次逼進 : loss 0.037868
第120次逼進 : loss 0.137415
測試精準度(accuracy): 1.000000

發佈留言

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