最近一直在學習李宏毅老師的機器學習視頻教程,學到和神經網絡那一塊知識的時候,我覺得單純的學習理論知識過於枯燥,就想着自己動手實現一些簡單的Demo,畢竟實踐是檢驗真理的唯一標准!!!但是網上很多的與tensorflow或者神經網絡相關的Demo教程都只是在驗證官方程序的過程,而如何把這些程序變成自己可以真正利用的程序這一塊的資料就比較少,就好比被“玩爛的"MNIST數據集(ML界的”hello world"),網上是有很多手寫數字識別的教程,但那些利用的都是官方提供的數據集,這樣就算驗證成功了帶來的滿足感還是遠遠不夠!廢話不多說,接下來就讓我來介紹一下如何使用Tensorflow和MNIST識別自己寫的數字(比如下圖這個我寫的數字5~~)
本文也參考了某些大神博客的內容。希望能幫助和我一樣剛剛起步的同學,大家多多指教。
相應的代碼和官方以及自己的數據集:https://github.com/tgpcai/digit_recognition
目錄:
(1)MNIST數據集簡介
(2)利用MNIST數據集訓練模型
(3)自己手寫數字,並用matlab進行預處理
(4)將圖片輸入網絡進行識別
(5)實踐過程遇到的坑與總結
(1)MNIST數據集簡介
既然我們要構建自己的數據集,那我們就必須要了解官方提供的數據集的格式,大小等一些特征。MNIST是一個巨大的手寫數字數據集,被廣泛應用於機器學習識別領域。MNIST有60000張訓練集數據和10000張測試集數據,每一個訓練元素都是28*28像素的手寫數字圖片,而且都是黑白色構成(這里的黑色是一個0-1的浮點數,黑色越深表示數值越靠近1)。在網上搜索一下MNIST,你可以發現圖片長這樣:
上圖就是4張MNIST圖片。這些圖片並不是傳統意義上的png或者jpg格式的圖片,因為png或者jpg的圖片格式,會帶有很多干擾信息,所以我們在創建自己的數據集的時候就必須進行預處理。
划重點:28*28像素,灰度圖
(2)利用MNIST數據集訓練模型,並保存模型
該Demo使用的模型主要是CNN卷積神經網絡,該模型廣泛應用於圖片識別、自然語言處理等方向。有關CNN卷積神經網絡的知識在我的其他博客中有詳細介紹,歡迎大家一起交流!上代碼:
1 import tensorflow as tf 2 from tensorflow.examples.tutorials.mnist import input_data 3 4 5 #定義初始化權重的函數 6 def weight_variavles(shape): 7 w = tf.Variable(tf.truncated_normal(shape, stddev=0.1)) 8 return w 9 10 #定義一個初始化偏置的函數 11 def bias_variavles(shape): 12 b = tf.Variable(tf.constant(0.1, shape=shape)) 13 return b 14 15 16 def model(): 17 18 #1.建立數據的占位符 x [None, 784] y_true [None, 10] 19 with tf.variable_scope("date"): 20 x = tf.placeholder(tf.float32, [None, 784]) 21 22 y_true = tf.placeholder(tf.float32, [None, 10]) 23 24 #2.卷積層1 卷積:5*5*1,32個filter,strides= 1-激活-池化 25 with tf.variable_scope("conv1"): 26 #隨機初始化權重 27 w_conv1 = weight_variavles([5, 5, 1, 32]) 28 b_conv1 = bias_variavles([32]) 29 30 #對x進行形狀的改變[None, 784] ----- [None,28,28,1] 31 x_reshape = tf.reshape(x,[-1, 28, 28, 1]) #不能填None,不知道就填-1 32 33 # [None,28, 28, 1] -------- [None, 28, 28, 32] 34 x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding = "SAME") + b_conv1) 35 36 #池化 2*2,步長為2,【None, 28,28, 32]--------[None,14, 14, 32] 37 x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME") 38 39 #3.卷積層2 卷積:5*5*32,64個filter,strides= 1-激活-池化 40 with tf.variable_scope("conv2"): 41 #隨機初始化權重和偏置 42 w_conv2 = weight_variavles([5, 5, 32, 64]) 43 b_conv2 = bias_variavles([64]) 44 45 #卷積、激活、池化 46 #[None,14, 14, 32]----------【NOne, 14, 14, 64] 47 x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, w_conv2,strides=[1, 1, 1, 1], padding = "SAME") + b_conv2) 48 49 #池化 2*2,步長為2 【None, 14,14,64]--------[None,7, 7, 64] 50 x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME") 51 52 #4.全連接層 [None,7, 7, 64] --------- [None, 7*7*64] * [7*7*64, 10]+[10] = [none, 10] 53 with tf.variable_scope("fc"): 54 #隨機初始化權重和偏置: 55 w_fc = weight_variavles([7 * 7 * 64, 1024]) 56 b_fc = bias_variavles([1024]) 57 58 #修改形狀 [none, 7, 7, 64] ----------[None, 7*7*64] 59 x_fc_reshape = tf.reshape(x_pool2,[-1,7 * 7 * 64]) 60 h_fc1 = tf.nn.relu(tf.matmul(x_fc_reshape, w_fc) + b_fc) 61 62 # 在輸出之前加入dropout以減少過擬合 63 keep_prob = tf.placeholder("float") 64 h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) 65 66 w_fc1 = weight_variavles([1024, 10]) 67 b_fc1 = bias_variavles([10]) 68 69 #進行矩陣運算得出每個樣本的10個結果[NONE, 10],輸出 70 y_predict = tf.nn.softmax(tf.matmul(h_fc1_drop, w_fc1) + b_fc1) 71 72 return x, y_true, y_predict,keep_prob 73 74 75 def conv_fc(): 76 #獲取數據,MNIST_data是樓主用來存放官方的數據集,如果你要這樣表示的話,那MNIST_data這個文件夾應該和這個python文件在同一目錄 77 mnist = input_data.read_data_sets('MNIST_data', one_hot=True) 78 79 #定義模型,得出輸出 80 x,y_true,y_predict,keep_prob = model() 81 82 #進行交叉熵損失計算 83 #3.計算交叉熵損失 84 with tf.variable_scope("soft_cross"): 85 #求平均交叉熵損失,tf.reduce_mean對列表求平均值 86 loss = -tf.reduce_sum(y_true*tf.log(y_predict)) 87 88 #4.梯度下降求出最小損失,注意在深度學習中,或者網絡層次比較復雜的情況下,學習率通常不能太高 89 with tf.variable_scope("optimizer"): 90 91 train_op = tf.train.AdamOptimizer(1e-4).minimize(loss) 92 93 #5.計算准確率 94 with tf.variable_scope("acc"): 95 96 equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1)) 97 #equal_list None個樣本 類型為列表1為預測正確,0為預測錯誤[1, 0, 1, 0......] 98 99 accuray = tf.reduce_mean(tf.cast(equal_list, tf.float32)) 100 101 init_op = tf.global_variables_initializer() 102 103 saver = tf.train.Saver() 104 105 #開啟會話運行 106 with tf.Session() as sess: 107 sess.run(init_op) 108 for i in range(3000): 109 mnist_x, mnist_y = mnist.train.next_batch(50) 110 if i%100 == 0: 111 # 評估模型准確度,此階段不使用Dropout 112 train_accuracy = accuray.eval(feed_dict={x:mnist_x, y_true: mnist_y, keep_prob: 1.0}) 113 print("step %d, training accuracy %g"%(i, train_accuracy)) 114 115 # 訓練模型,此階段使用50%的Dropout 116 train_op.run(feed_dict={x:mnist_x, y_true: mnist_y, keep_prob: 0.5}) 117 # 將模型保存在你自己想保存的位置 118 saver.save(sess, "D:/Dict/model/fc_model.ckpt") 119 120 return None 121 122 if __name__ == "__main__": 123 conv_fc()
然后在你保存模型的目錄下會產生4個文件
.data文件是用來記錄權重,偏置等參數信息;.meta是用來記錄tensorflow圖的結構。以下是我的電腦的結果圖:
我只運行了6000次,按照tensorflow官方文檔,運行9000次左右可以達到0.992左右的正確率
(3)自己手寫數字,並用matlab進行預處理
首先讓我們看一下預處理的結果:->
->
具體過程也就是分為3個步驟:縮小它的大小為28*28像素,並轉變為灰度圖,最后進行二值化處理。具體matlab的代碼如下:
clear all; close all; clc; % 改圖片像素為28*28 I=imread('5.jpg'); %你自己手寫的數字的圖片 J=imresize(I,[28,28]); imshow(I); figure; imshow(J); imwrite(J,'new5.jpp');%生成28*28手寫數字圖片
接下來進行灰度與二值化處理
clear all;close all;clc; % Read an input image A = imread('new5.jpg'); % Convert the image to single-channel grayscale image A_gray = rgb2gray(A); figure,imhist(A_gray),title('hist of A_grey'); % Convert image to double i.e., [0,1] A_gray = im2double(A_gray); % Generate threhold value using Otsu's algorithm otsu_level = graythresh(A_gray); % Threshold image using Otsu's threshold and manually defined % threshold values B_otsu_thresh = im2bw(A_gray, otsu_level); B_thresh_50 = im2bw(A_gray, 50/255); B_thresh_100 = im2bw(A_gray, 100/255); B_thresh_150 = im2bw(A_gray, 150/255); B_thresh_200 = im2bw(A_gray, 200/255); % Display original and thresholded binary images side-by-side figure, subplot(2, 3, 1), imshow(A_gray), title('Original image'); subplot(2, 3, 2), imshow(B_otsu_thresh), title('Binary image using Otsu threshold value'); subplot(2, 3, 3), imshow(B_thresh_50), title('Binary image using threshold value = 50'); subplot(2, 3, 4), imshow(B_thresh_100), title('Binary image using threshold value = 100'); subplot(2, 3, 5), imshow(B_thresh_150), title('Binary image using threshold value = 150'); subplot(2, 3, 6), imshow(B_thresh_200), title('Binary image using threshold value = 200'); imwrite(B_otsu_thresh,'newnew5.jpg');%填寫你希望最終生成的數據集的名字和路徑
到此就完成了對自己手寫圖片的預處理過程!
預處理的方法有很多,在這我在介紹一種利用OPENCV進行預處理:
import cv2 global img global point1, point2 def on_mouse(event, x, y, flags, param): global img, point1, point2 img2 = img.copy() if event == cv2.EVENT_LBUTTONDOWN: #左鍵點擊 point1 = (x,y) cv2.circle(img2, point1, 10, (0,255,0), 5) cv2.imshow('image', img2) elif event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_LBUTTON): #按住左鍵拖曳 cv2.rectangle(img2, point1, (x,y), (255,0,0), 5) # 圖像,矩形頂點,相對頂點,顏色,粗細 cv2.imshow('image', img2) elif event == cv2.EVENT_LBUTTONUP: #左鍵釋放 point2 = (x,y) cv2.rectangle(img2, point1, point2, (0,0,255), 5) cv2.imshow('image', img2) min_x = min(point1[0], point2[0]) min_y = min(point1[1], point2[1]) width = abs(point1[0] - point2[0]) height = abs(point1[1] -point2[1]) cut_img = img[min_y:min_y+height, min_x:min_x+width] resize_img = cv2.resize(cut_img, (28,28)) # 調整圖像尺寸為28*28 ret, thresh_img = cv2.threshold(resize_img,127,255,cv2.THRESH_BINARY) # 二值化 cv2.imshow('result', thresh_img) cv2.imwrite('new5.jpg', thresh_img) # 預處理后圖像保存位置 def main(): global img img = cv2.imread('5.jpg') # 手寫數字圖像所在位置 img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 轉換圖像為單通道(灰度圖) cv2.namedWindow('image') cv2.setMouseCallback('image', on_mouse) # 調用回調函數 cv2.imshow('image', img) cv2.waitKey(0) if __name__ == '__main__': main()
以上兩種方法都可以,甚至還有大神利用PS自己生成數據集,感興趣的同學可以自己去搜索一下~
(4)將圖片輸入網絡進行識別
完成圖像預處理后,即可將圖片輸入到網絡中進行識別
1 from PIL import Image, ImageFilter 2 import tensorflow as tf 3 import matplotlib.pyplot as plt 4 5 6 def imageprepare(): 7 im = Image.open('C:/Users/tgp/Desktop/newnew5.jpg') 8 plt.imshow(im) 9 data = list(im.getdata()) 10 result = [(255-x)*1.0/255.0 for x in data] 11 return result 12 13 14 #定義初始化權重的函數 15 def weight_variavles(shape): 16 w = tf.Variable(tf.truncated_normal(shape, stddev=0.1)) 17 return w 18 19 #定義一個初始化偏置的函數 20 def bias_variavles(shape): 21 b = tf.Variable(tf.constant(0.0, shape=shape)) 22 return b 23 24 25 def model(): 26 tf.reset_default_graph() 27 #1.建立數據的占位符 x [None, 784] y_true [None, 10] 28 with tf.variable_scope("date"): 29 x = tf.placeholder(tf.float32, [None, 784]) 30 31 #y_true = tf.placeholder(tf.float32, [None, 10]) 32 33 34 35 #2.卷積層1 卷積:5*5*1,32個filter,strides= 1-激活-池化 36 with tf.variable_scope("conv1"): 37 #隨機初始化權重 38 w_conv1 = weight_variavles([5, 5, 1, 32]) 39 b_conv1 = bias_variavles([32]) 40 41 #對x進行形狀的改變[None, 784] ----- [None,28,28,1] 42 x_reshape = tf.reshape(x,[-1, 28, 28, 1]) #不能填None,不知道就填-1 43 44 # [None,28, 28, 1] -------- [None, 28, 28, 32] 45 x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding = "SAME") + b_conv1) 46 47 #池化 2*2,步長為2,【None, 28,28, 32]--------[None,14, 14, 32] 48 x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME") 49 50 #3.卷積層2 卷積:5*5*32,64個filter,strides= 1-激活-池化 51 with tf.variable_scope("conv2"): 52 #隨機初始化權重和偏置 53 w_conv2 = weight_variavles([5, 5, 32, 64]) 54 b_conv2 = bias_variavles([64]) 55 56 #卷積、激活、池化 57 #[None,14, 14, 32]----------【NOne, 14, 14, 64] 58 x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, w_conv2,strides=[1, 1, 1, 1], padding = "SAME") + b_conv2) 59 60 #池化 2*2,步長為2 【None, 14,14,64]--------[None,7, 7, 64] 61 x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1],strides = [1,2,2,1],padding = "SAME") 62 63 #4.全連接層 [None,7, 7, 64] --------- [None, 7*7*64] * [7*7*64, 10]+[10] = [none, 10] 64 with tf.variable_scope("fc"): 65 #隨機初始化權重和偏置: 66 w_fc = weight_variavles([7 * 7 * 64, 1024]) 67 b_fc = bias_variavles([1024]) 68 69 #修改形狀 [none, 7, 7, 64] ----------[None, 7*7*64] 70 x_fc_reshape = tf.reshape(x_pool2,[-1,7 * 7 * 64]) 71 h_fc1 = tf.nn.relu(tf.matmul(x_fc_reshape, w_fc) + b_fc) 72 73 keep_prob = tf.placeholder("float") 74 h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) 75 76 w_fc1 = weight_variavles([1024, 10]) 77 b_fc1 = bias_variavles([10]) 78 79 #進行矩陣運算得出每個樣本的10個結果[NONE, 10] 80 #y_predict = tf.matmul(h_fc1_drop, w_fc1) + b_fc1 81 y_predict = tf.nn.softmax(tf.matmul(h_fc1_drop, w_fc1) + b_fc1) 82 return x, y_predict,keep_prob 83 84 85 def conv_fc(): 86 #獲取數據 87 result = imageprepare() 88 89 #定義模型,得出輸出 90 x,y_predict,keep_prob = model() 91 92 init_op = tf.global_variables_initializer() 93 94 saver = tf.train.Saver() 95 96 #開啟會話運行 97 #tf.reset_default_graph() 98 with tf.Session() as sess: 99 sess.run(init_op) 100 print(result) 101 saver.restore(sess, "D:/Dict/model/fc_model.ckpt") 102 prediction = tf.argmax(y_predict,1) 103 predint = prediction.eval(feed_dict={x: [result],keep_prob: 1.0}, session=sess) 104 print(predint) 105 print("recognize result: %d" %predint[0]) 106 107 108 return None 109 110 if __name__ == "__main__": 111 conv_fc()
運行結果如下:
(5)實踐過程遇到的坑與總結
- 剛開始寫訓練模型的代碼的時候,我認為不需要防止過擬合這個處理過程,所以在我的模型里面沒有防止過擬合這一操作,直接導致的結果是:在訓練模型的時候效果非常不錯,但是當真正拿自己手寫數字去識別的時候,經常把‘4’和‘9’搞錯。隨便我在輸出層和全連接層中間添加了一些代碼用於防止過擬合,這樣訓練出的模型表現結果尚佳!由此可見,在訓練模型的時候防止過擬合的操作還是非常有必要的。
- 有關隨機初始化權重和偏置的函數的選擇:利用tf.truncated_normal()這個函數隨機初始化權重訓練出的模型的表現效果比利用tf.random_nomal()這個函數訓練出的模型表現的更好,上網查詢了一下,發現這兩個函數有一下的區別:tf.truncated_normal的輸出如字面意思是截斷的,而截斷的標准是2倍的stddev。使用tf.truncated_normal的輸出是不可能出現[-2,2]以外的點的,而如果shape夠大的話,tf.random_normal卻會產生2.2或者2.4之類的輸出。也就是說使用tf.random_normal產生的初始權重的值比tf.truncated_normal產生的大,這對於神經網絡而言是致命的,因為這樣非常容易產生梯度消失的問題。
-
在隨機初始化權重和偏置的時候,方差不能設置的過大,若方差過大,則在訓練的時候准確率一直維持在很低的位置,容易產生梯度消失的問題。
- 保存模型盡量以.ckpt結果,反正樓主一開始沒有以.ckpt結尾,帶來了很多麻煩,然后加上這個后綴,啥問題都消失了~(可能是玄學,不加可能也行的通,但是加了一定不會錯~~)
以上就是本次實踐的全部過程,歡迎大家交流討論。
參考:https://www.cnblogs.com/lizheng114/p/7498328.html