神經網路(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