關於LeNet5
LeNet-5是一個簡單的卷積神經網絡,是用於手寫字體的識別的一個經典CNN
前向傳播過程如下:
INPUT層
這是神經網絡的輸入,輸入圖像的尺寸統一為32×32。
C1層
輸入圖片:32×32
卷積核大小:5×5
卷積核種類:6
輸出feature map大小:28×28
神經元數量:28×28×6
可訓練參數:(5×5+1) × 6(每個濾波器5×5=25個參數和一個bias參數,一共6個濾波器)
S2層
輸入:28×28
采樣區域:2×2
采樣種類:6
輸出feature Map大小:14×14(28/2)
神經元數量:14×14×6
C3層
輸入:14×14×6
卷積核大小:5×5
卷積核種類:16
輸出feature Map大小:10×10 ((14-5+1)=10)
S4層
輸入:10×10
采樣區域:2×2
采樣種類:16
輸出feature Map大小:5×5(10/2)
神經元數量:5×5×16=400
C5層
輸入:S4層的全部16個單元特征map(與s4全相連)
卷積核大小:5×5
卷積核種類:120
輸出feature Map大小:1×1(5-5+1)
F6層
輸入:c5 120維向量
計算方式:計算輸入向量和權重向量之間的點積,再加上一個偏置,結果通過sigmoid函數輸出。
有84個神經元
可訓練參數:84×(120+1)=10164
OUTPUT層
Output層也是全連接層,共有10個神經元,分別代表數字0到9,且如果節點i的值為0,則網絡識別的結果是數字i
代碼實現
import numpy as np import tensorflow as tf from tensorflow.python.framework import graph_util from PIL import Image import os '''構建數據集''' # 第一次遍歷圖片目錄是為了獲取圖片總數 input_count = 0 for i in range(0, 10): dir = './mnist_digits_images/%s/' % i # 這里可以改成你自己的圖片目錄,i為分類標簽 for rt, dirs, files in os.walk(dir): for filename in files: input_count += 1 # 定義對應維數和各維長度的數組 input_images = np.array([[0] * 1024 for i in range(input_count)]) input_labels = np.array([[0] * 10 for i in range(input_count)]) # 第二次遍歷圖片目錄是為了生成圖片數據和標簽 index = 0 for i in range(0, 10): dir = './mnist_digits_images/%s/' % i # 這里可以改成你自己的圖片目錄,i為分類標簽 for rt, dirs, files in os.walk(dir): for filename in files: filename = dir + filename img = Image.open(filename) width = img.size[0] height = img.size[1] for h in range(0, height): for w in range(0, width): # 通過這樣的處理,使數字的線條變細,有利於提高識別准確率 if img.getpixel((w, h)) > 230: input_images[index][w + h * width] = 0 # 之前已經將圖片轉換成了一維 else: input_images[index][w + h * width] = 1 input_labels[index][i] = 1 index += 1 '''定義占位符''' # 定義輸入節點,對應於圖片像素值矩陣集合和圖片標簽(即所代表的數字) x = tf.placeholder(tf.float32, shape=[None, 1024]) y_ = tf.placeholder(tf.float32, shape=[None, 10]) x_image = tf.reshape(x, [-1, 32, 32, 1]) '''定義權重和偏置''' ''' 輸入矩陣格式:四個維度,依次為:樣本數、圖像高度、圖像寬度、圖像通道數 輸出矩陣格式:與輸出矩陣的維度順序和含義相同,但是后三個維度(圖像高度、圖像寬度、圖像通道數)的尺寸發生變化。 權重矩陣(卷積核)格式:同樣是四個維度,但維度的含義與上面兩者都不同,為:卷積核高度、卷積核寬度、輸入通道數、輸出通道數(卷積核個數) 輸入矩陣、權重矩陣、輸出矩陣這三者之間的相互決定關系 卷積核的輸入通道數(in depth)由輸入矩陣的通道數所決定。(紅色標注) 輸出矩陣的通道數(out depth)由卷積核的輸出通道數所決定。(綠色標注) 輸出矩陣的高度和寬度(height, width)這兩個維度的尺寸由輸入矩陣、卷積核、掃描方式所共同決定。計算公式如下。(藍色標注) ''' '''構建網絡''' # 構建第1個卷積層 W_conv1 = tf.Variable(tf.truncated_normal([5, 5, 1, 6], stddev=0.1)) # 定義卷積核 b_conv1 = tf.Variable(tf.constant(0.1, shape=[6])) # 定義偏置 L1_conv = tf.nn.conv2d(x_image, W_conv1, strides=[1, 1, 1, 1], padding='VALID') # 進行卷積操作 L1_relu = tf.nn.relu(L1_conv + b_conv1) # 通過激活函數 L1_pool = tf.nn.max_pool(L1_relu, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID') # 進行池化操作 # 構建第2個卷積層 W_conv2 = tf.Variable(tf.truncated_normal([5, 5, 6, 16], stddev=0.1)) # 定義卷積核 b_conv2 = tf.Variable(tf.constant(0.1, shape=[16])) # 定義偏置 L2_conv = tf.nn.conv2d(L1_pool, W_conv2, strides=[1, 1, 1, 1], padding='VALID') # 進行卷積操作 L2_relu = tf.nn.relu(L2_conv + b_conv2) # 通過激活函數 L2_pool = tf.nn.max_pool(L2_relu, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID') # 進行池化操作 # 構建第1個全連接層 W_fc1 = tf.Variable(tf.truncated_normal([5 * 5 * 16, 120], stddev=0.1)) b_fc1 = tf.Variable(tf.constant(0.1, shape=[120])) h_pool2_flat = tf.reshape(L2_pool, [-1, 5 * 5 * 16]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) # 構建第2個全連接層 W_fc2 = tf.Variable(tf.truncated_normal([120, 84], stddev=0.1)) b_fc2 = tf.Variable(tf.constant(0.1, shape=[84])) h_fc2 = tf.nn.relu(tf.matmul(h_fc1, W_fc2) + b_fc2) # 構建output層 W_out = tf.Variable(tf.truncated_normal([84, 10], stddev=0.1)) b_out = tf.Variable(tf.constant(0.1, shape=[10])) y_conv = tf.matmul(h_fc2, W_out) + b_out pred = tf.nn.softmax(y_conv, name='out_softmax') #[n, 10] # 定義損失函數 cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv)) # 運用梯度下降算法減小損失,學習率設置為0.0001 train_step = tf.train.AdamOptimizer((1e-4)).minimize(cross_entropy) # 將預測值與真實值的比較結果存放在一個布爾型列表中, correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) # 計算這一批次的准確率 accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) with tf.Session() as sess: # 初始化所有變量 sess.run(tf.global_variables_initializer()) print("一共讀取了 %s 個輸入圖像, %s 個標簽" % (input_count, input_count)) # 設置每次訓練op的輸入個數和迭代次數,這里為了支持任意圖片總數,定義了一個余數remainder,譬如,如果每次訓練op的輸入個數為60,圖片總數為150張,則前面兩次各輸入60張,最后一次輸入30張(余數30) batch_size = 60 iterations = 500 batches_count = int(input_count / batch_size) remainder = input_count % batch_size print("數據集分成 %s 批, 前面每批 %s 個數據,最后一批 %s 個數據" % (batches_count + 1, batch_size, remainder)) # 執行訓練迭代 for it in range(iterations): # 這里的關鍵是要把輸入數組轉為np.array for n in range(batches_count): aa = input_images[n * batch_size:(n + 1) * batch_size] train_step.run(feed_dict={x: input_images[n * batch_size:(n + 1) * batch_size], y_: input_labels[n * batch_size:(n + 1) * batch_size]}) if remainder > 0: start_index = batches_count * batch_size; train_step.run( feed_dict={x: input_images[start_index:input_count - 1], y_: input_labels[start_index:input_count - 1]}) # 保存模型 constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ["out_softmax"]) # out_softmax with tf.gfile.FastGFile("model.pb", mode='wb') as f: f.write(constant_graph.SerializeToString()) # 每完成五次迭代,判斷准確度是否已達到100%,達到則退出迭代循環 iterate_accuracy = 0 if it % 5 == 0: iterate_accuracy = accuracy.eval(feed_dict={x: input_images, y_: input_labels}) print('iteration %d: accuracy %s' % (it, iterate_accuracy)) if iterate_accuracy >= 1: break; print('完成訓練!')
注意:如果想要用opencv進行調用pb模型,輸入參數只能有一個,也就是placeholder的輸入只能有一個,一般keep_prob的參數也需要是placeholder,試了一下產出的模型,opencv不能進行調用,暫時還沒找到解決辦法,只好將placeholder只保留一個。
opencv-python調用代碼
import cv2 import numpy as np inference_pb = "model.pb" net = cv2.dnn.readNetFromTensorflow(inference_pb) frame = cv2.imread("0.jpg", 0) net.setInput(cv2.dnn.blobFromImage(frame, size=(32, 32), swapRB=True, crop=False)) cvOut = net.forward() print(cvOut) cvOut = np.argmax(cvOut[0]) print(cvOut) cv2.imshow("a", frame) cv2.waitKey(-1)