Tensorflow實現LeNet5網絡並保存pb模型,實現自定義的手寫數字識別(附opencv-python調用代碼)


關於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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM