py4CV例子3Mnist識別和ANN


1、什么是mnist數據集; 
     mnist是一個被重度使用的數字手寫字符集。 它來自美國國家標准與技術研究所, National Institute of Standards and Technology (NIST). 訓練集 (training set) 由來自 250 個不同人手寫的數字構成, 其中 50% 是高中學生, 50% 來自人口普查局 (the Census Bureau) 的工作人員. 測試集(test set) 也是同樣比例的手寫數字數據
     官網鏈接:http://yann.lecun.com/exdb/mnist/  它提供了六萬的訓練集和一萬的測試集。其數據 是被規范處理過的,每一張都是被放在中間部位的28px*28px的灰度圖。
        總共4個文件,名字也很直接:
         train-images-idx3-ubyte: training set images
         train-labels-idx1-ubyte: training set labels
         t10k-images-idx3-ubyte: test set images
         t10k-labels-idx1-ubyte: test set labels
     圖片都被轉成二進制放到了文件里面,讀取的時候要按照固定方法來讀。
這里寫圖片描述

2、什么是ANN 

     人工神經網絡( Artificial Neural Network,簡稱ANN )是指由大量的處理單元(神經元) 互相連接而形成的復雜網絡結構,是對人腦組織結構和運行機制的某種抽象、簡化和模擬。這里我們用ANN指小型神經網絡 ,也經常被叫做多層感知器(Multi-layer Perceptron MLP)。
      神經網絡是具有適應性的簡單單元組成的廣泛並行互聯的網絡。它的組織能夠模擬生物神經系統對真實世界物體做做出的反應。神經網絡的最基本的成分是神經元模型,也就是最簡單的神經元模型。
     
神經元接收到來自n個其他神經元傳遞過來的輸入信號,這些信號通過帶權重的鏈接進行傳遞。神經元接收到的總輸入值將與神經元的閾值進行比較,然后通過“激活函數”處理以產生神經元的輸出。

理想的激活函數應該是階躍函數,也就是它能夠將輸入值映射成為輸出值0或1。其中“0”代表神經元抑制,“1”代表神經元興奮。但是由於階躍函數不連續且不可導,因此實際上常常使用sigmoid函數當做神經元的激活函數。它能夠將可能在較大范圍內變化的輸出值擠壓到(0,1)之間這個范圍內。因此有時也成為擠壓函數。常用的sigmoid函數是回歸函數。
f(x) = 1/(1+e^(-x))


    ANN是最簡單的神經網絡,它由幾層神經元組成。輸入層接受外界信號后傳遞給輸出層。輸出層是M-P神經元。感知機也成為閾值邏輯單元。感知機可以通過采用監督學習來逐步增強模式划分的能力,達到學習的目的。一般來說,默認認為ANN(MLP)都是3層網絡,但是每層的元可能比較多,比如像這樣
這里寫圖片描述

BP算法(誤差逆傳播算法)是神經網絡在發展到中間階段的時候重要的理論和運用成果。
•在感知器算法中我們實際上是在利用理想輸出與實際輸出之間的誤差作為增量來修正權值,然而在多層感知器中,我們只能計算出輸出層的誤差,中間隱層由於不直接與外界連接,其誤差無法估計。
•反向傳播算法(BP算法)的思想:從后向前反向逐層傳播輸出層的誤差,以間接計算隱層的誤差。算法可以分為兩個階段:
–正向過程:從輸入層經隱層逐層正向計算各單元的輸出;
–反向過程:由輸出誤差逐層反向計算隱層各單元的誤差,並用此誤差修正前層的權值。
B-P算法的學習過程如下:
(1)選擇一組訓練樣例,每一個樣例由輸入信息和期望的輸出結果兩部分組成。
(2)從訓練樣例集中取一樣例,把輸入信息輸入到網絡中。
(3)分別計算經神經元處理后的各層節點的輸出。
(4)計算網絡的實際輸出和期望輸出的誤差。
(5)從輸出層反向計算到第一個隱層,並按照某種能使誤差向減小方向發展的原則,調整網絡中各神經元的連接權值。
(6)對訓練樣例集中的每一個樣例重復(3)-(5)的步驟,直到對整個訓練樣例集的誤差達到要求時為止。

•優點:
–理論基礎牢固,推導過程嚴謹,物理概念清晰,通用性好等。所以,它是目前用來訓練前饋多層網絡較好的算法。
•缺點:
–BP算法的收斂速度一般來說比較慢;
–BP算法只能收斂於局部最優解,不能保證收斂於全局最優解;
–當隱層元的數量足夠多時,網絡對訓練樣本的識別率很高,但對測試樣本的識別率有可能很差,即網絡的推廣能力有可能較差。
3、 OpenCV+Python實現ANN算法  
import cv2
import numpy as np

ann = cv2.ml.ANN_MLP_create()
ann.setLayerSizes(np.array([ 9, 5, 9], dtype=np.uint8))
ann.setTrainMethod(cv2.ml.ANN_MLP_BACKPROP)
ann.train(np.array([[ 1.2, 1.3, 1.9, 2.2, 2.3, 2.9, 3.0, 3.2, 3.3]], dtype=np.float32),
cv2.ml.ROW_SAMPLE,
np.array([[ 0, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=np.float32))
print(ann.predict(np.array([[ 1.4, 1.5, 1.2, 2., 2.5, 2.8, 3., 3.1, 3.8]], dtype=np.float32)))

          
其中
ann = cv2.ml.ANN_MLP_create()
創建ann,可以看到opencv中,ann和mlp就是一個意思
ann.setLayerSizes(np.array([ 9, 5, 9], dtype=np.uint8))
ann.setTrainMethod(cv2.ml.ANN_MLP_BACKPROP)
設置參數,為959三層網絡,並且使用反向傳播算法。
ann.train(np.array([[ 1.2, 1.3, 1.9, 2.2, 2.3, 2.9, 3.0, 3.2, 3.3]], dtype=np.float32),
cv2.ml.ROW_SAMPLE,
np.array([[ 0, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=np.float32))
注意這里,ann的train包含三個參數:samples、layout和responses。只有samples是必須設置的,另兩個可選。如果不提供 layout和responses采用無監督算法,如果提供則是 layout和responses有監督算法。我們實現mnist肯定是監督的。 由於是959,所以輸入9個數據。     
完整請參考 https://docs.opencv.org/master/d0/dce/classcv_1_1ml_1_1ANN__MLP.html
輸入原始數據,訓練。由於是959,所以輸入9個數據。
print(ann.predict(np.array([[ 1.4, 1.5, 1.2, 2., 2.5, 2.8, 3., 3.1, 3.8]], dtype=np.float32)))
進行預測並且打印結果
由於使用了ANN,所以整個算法的結構和前面knn/svm都是非常相識的,這一點新版本的OpenCV做的相當好。
(5.0, array([[-0.06419383, -0.13360272, -0.1681568 , -0.18708915, 0.0970564 ,
0.89237726, 0.05093023, 0.17537238, 0.13388439]],
dtype=float32))
這個例子不能說明任何問題,只能給證明opencv中ann是正確安裝的。
4、 ANN實現動物分類
注意,我們只采用了模擬數據,以理清楚語法為主要目標
import cv2
import numpy as np
from random import randint

animals_net = cv2.ml.ANN_MLP_create()
animals_net.setTrainMethod(cv2.ml.ANN_MLP_RPROP | cv2.ml.ANN_MLP_UPDATE_WEIGHTS)
animals_net.setActivationFunction(cv2.ml.ANN_MLP_SIGMOID_SYM)
animals_net.setLayerSizes(np.array([ 3, 8, 4]))
animals_net.setTermCriteria(( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 ))

"""Input arrays
weight, length, teeth
"""

"""Output arrays
dog, eagle, dolphin and dragon
"""

def dog_sample():
return [randint( 10, 20), 1, randint( 38, 42)]

def dog_class():
return [ 1, 0, 0, 0]

def condor_sample():
return [randint( 3, 10), randint( 3, 5), 0]

def condor_class():
return [ 0, 1, 0, 0]

def dolphin_sample():
return [randint( 30, 190), randint( 5, 15), randint( 80, 100)]

def dolphin_class():
return [ 0, 0, 1, 0]

def dragon_sample():
return [randint( 1200, 1800), randint( 30, 40), randint( 160, 180)]

def dragon_class():
return [ 0, 0, 0, 1]

def record( sample, classification):
return (np.array([sample], dtype=np.float32), np.array([classification], dtype=np.float32))

records = []

"""
SAMPLES = 5000
for x in range(0, SAMPLES):
print "Samples %d/%d" % (x, SAMPLES)
animals_net.train(np.array([dog_sample()], dtype=np.float32), cv2.ml.ROW_SAMPLE, np.array([dog_class()], dtype=np.float32))
animals_net.train(np.array([condor_sample()], dtype=np.float32), cv2.ml.ROW_SAMPLE, np.array([condor_class()], dtype=np.float32))
animals_net.train(np.array([dolphin_sample()], dtype=np.float32), cv2.ml.ROW_SAMPLE, np.array([dolphin_class()], dtype=np.float32))
animals_net.train(np.array([dragon_sample()], dtype=np.float32), cv2.ml.ROW_SAMPLE, np.array([dragon_class()], dtype=np.float32))
"""

RECORDS = 5000
for x in range( 0, RECORDS):
records.append(record(dog_sample(), dog_class()))
records.append(record(condor_sample(), condor_class()))
records.append(record(dolphin_sample(), dolphin_class()))
records.append(record(dragon_sample(), dragon_class()))

EPOCHS = 2
for e in range( 0, EPOCHS):
print( "Epoch %d :" % e)
for t, c in records:
animals_net.train(t, cv2.ml.ROW_SAMPLE, c)


TESTS = 100
dog_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([dog_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 0:
dog_results += 1

condor_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([condor_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 1:
condor_results += 1

dolphin_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([dolphin_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 2:
dolphin_results += 1

dragon_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([dragon_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 3:
dragon_results += 1

print ( "Dog accuracy: %f%% " % (dog_results))
print ( "condor accuracy: %f%% " % (condor_results))
print ( "dolphin accuracy: %f%% " % (dolphin_results))
print ( "dragon accuracy: %f%% " % (dragon_results))

其中
animals_net = cv2.ml.ANN_MLP_create()
animals_net.setTrainMethod(cv2.ml.ANN_MLP_RPROP | cv2.ml.ANN_MLP_UPDATE_WEIGHTS)
animals_net.setActivationFunction(cv2.ml.ANN_MLP_SIGMOID_SYM)
animals_net.setLayerSizes(np.array([ 3, 8, 4]))
animals_net.setTermCriteria(( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 ))
創建ANN,設定為彈性(resilient)反向傳播,激勵函數為sigmod(這幾項都是前面那個例子所沒有的),並且層數為384,同時設定算法終止條件。
def dog_sample():
return [randint( 10, 20), 1, randint( 38, 42)]

def dog_class():
return [ 1, 0, 0, 0]

def condor_sample():
return [randint( 3, 10), randint( 3, 5), 0]

def condor_class():
return [ 0, 1, 0, 0]

def dolphin_sample():
return [randint( 30, 190), randint( 5, 15), randint( 80, 100)]

def dolphin_class():
return [ 0, 0, 1, 0]

def dragon_sample():
return [randint( 1200, 1800), randint( 30, 40), randint( 160, 180)]

def dragon_class():
return [ 0, 0, 0, 1]
定義四種創建樣本函數和四種分類函數,用來幫助訓練網絡。其中dog為類1,condor為類2,dolphin為類3,dragon為勒

RECORDS = 5000
for x in range( 0, RECORDS):
records.append(record(dog_sample(), dog_class()))
records.append(record(condor_sample(), condor_class()))
records.append(record(dolphin_sample(), dolphin_class()))
records.append(record(dragon_sample(), dragon_class()))

EPOCHS = 2
for e in range( 0, EPOCHS):
print( "Epoch %d :" % e)
for t, c in records:
animals_net.train(t, cv2.ml.ROW_SAMPLE, c)
直接將樣本生成函數和分類函數壓入模型。共創建4類動物數據,每類5k樣本。這些動物的向量都是隨機生產的(也就是特征都是沒有規律的)。這個例子只是為了進一步說明ann是如何實現效果的。這里只進行了2次迭代。那么訓練的迭代需要多少次?這個是需要具體研究的;
TESTS = 100
dog_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([dog_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 0:
dog_results += 1

condor_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([condor_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 1:
condor_results += 1

dolphin_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([dolphin_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 2:
dolphin_results += 1

dragon_results = 0
for x in range( 0, TESTS):
clas = int(animals_net.predict(np.array([dragon_sample()], dtype=np.float32))[ 0])
print( "class: %d " % clas)
if (clas) == 3:
dragon_results += 1

print ( "Dog accuracy: %f%% " % (dog_results))
print ( "condor accuracy: %f%% " % (condor_results))
print ( "dolphin accuracy: %f%% " % (dolphin_results))
print ( "dragon accuracy: %f%% " % (dragon_results))
將數據帶回去,比進行簡單測試(不是kflod)
Dog accuracy: 100.000000%
condor accuracy: 0.000000%
dolphin accuracy: 0.000000%
dragon accuracy: 68.000000%
   這個結果相當的糟糕,當然我們已經進一步知道了語法如何實現ann

5、 實現mnist
import cv2
import numpy as np
import digits_ann as ANN

#用來確定矩形是否完全包含在另一個矩形中
def inside( r1, r2):
x1,y1,w1,h1 = r1
x2,y2,w2,h2 = r2
if (x1 > x2) and (y1 > y2) and (x1+w1 < x2+w2) and (y1+h1 < y2 + h2):
return True
else:
return False
#用來獲取數字周圍的矩形,在數字上對其中心化,保證數字完全包含在正方形中
def wrap_digit( rect):
x, y, w, h = rect
padding = 5
hcenter = x + w/ 2
vcenter = y + h/ 2
roi = None
if (h > w):
w = h
x = hcenter - (w/ 2)
else:
h = w
y = vcenter - (h/ 2)
return (( int)(x-padding), ( int)(y-padding), ( int)(w+padding), ( int)(h+padding))

# 創建ann,隱藏層有58個節點,共5w樣本
ann, test_data = ANN.train(ANN.create_ANN( 58), 50000, 5)
font = cv2.FONT_HERSHEY_SIMPLEX

# 用來測試的識別圖像。這個例子需要添加的部分就是要做k-fold或者至少一次測試
path = "D:/dl4cv/workSpace/chapter9/images/numbers.jpg"
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
bw = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
bw = cv2.GaussianBlur(bw, ( 7, 7), 0)
ret, thbw = cv2.threshold(bw, 127, 255, cv2.THRESH_BINARY_INV)
thbw = cv2.erode(thbw, np.ones(( 2, 2), np.uint8), iterations = 2)
#通過輪廓來迭代
image, cntrs, hier = cv2.findContours(thbw.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

rectangles = []

for c in cntrs:
r = x,y,w,h = cv2.boundingRect(c)
a = cv2.contourArea(c)
b = (img.shape[ 0]- 3) * (img.shape[ 1] - 3)
is_inside = False
for q in rectangles:
if inside(r, q):
is_inside = True
break
if not is_inside:
if not a == b:
rectangles.append(r)

for r in rectangles:
x,y,w,h = wrap_digit(r)
cv2.rectangle(img, (x,y), (x+w, y+h), ( 0, 255, 0), 2)
roi = thbw[y:y+h, x:x+w]
try:
digit_class = int(ANN.predict(ann, roi.copy())[ 0]) #digits_ann as ANN
except:
continue
cv2.putText(img, " %d " % digit_class, (x, y- 1), font, 1, ( 0, 255, 0))

cv2.imshow( "thbw", thbw)
cv2.imshow( "contours", img)
cv2.imwrite( "sample.jpg", img)
cv2.waitKey()

以及
import cv2
import pickle
import numpy as np
import gzip
"""OpenCV ANN Handwritten digit recognition example

Wraps OpenCV's own ANN by automating the loading of data and supplying default paramters,
such as 20 hidden layers, 10000 samples and 1 training epoch.

The load data code is taken from http://neuralnetworksanddeeplearning.com/chap1.html
by Michael Nielsen
"""
"""
這是一個ann類庫,為了盡可能自動執行,我們進行封裝
"""

#讀取mnist.pkl.gz數據
def load_data():
mnist = gzip.open( 'D:/dl4cv/datesets/mnist.pkl.gz', 'rb')
#訓練集,校驗集和測試集 注意 pickle.load(mnist,encoding="bytes")
training_data, classification_data, test_data = pickle.load(mnist, encoding= "bytes")
mnist.close()
return (training_data, classification_data, test_data)

def wrap_data():
tr_d, va_d, te_d = load_data()
training_inputs = [np.reshape(x, ( 784, 1)) for x in tr_d[ 0]]
training_results = [vectorized_result(y) for y in tr_d[ 1]]
training_data = zip(training_inputs, training_results)

validation_inputs = [np.reshape(x, ( 784, 1)) for x in va_d[ 0]]
validation_data = zip(validation_inputs, va_d[ 1])

test_inputs = [np.reshape(x, ( 784, 1)) for x in te_d[ 0]]
test_data = zip(test_inputs, te_d[ 1])
#訓練集,校驗集和測試集,只有訓練集是有results的,也就是由監督的
return (training_data, validation_data, test_data)

#創建包含10個元素的零元組,在期望結果的位置設置1.這樣可以用作輸出層的類標簽
def vectorized_result( j):
e = np.zeros(( 10, 1))
e[j] = 1.0
return e

#創建一個用於手寫數字識別的ann
def create_ANN( hidden = 20):
ann = cv2.ml.ANN_MLP_create()
ann.setLayerSizes(np.array([ 784, hidden, 10]))
ann.setTrainMethod(cv2.ml.ANN_MLP_RPROP)
ann.setActivationFunction(cv2.ml.ANN_MLP_SIGMOID_SYM)
#截至條件
ann.setTermCriteria(( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 100, 1 ))
return ann

def train( ann, samples = 10000, epochs = 1):
tr, val, test = wrap_data()
#給定一定數量的樣本和訓練周期,加載數據,然后迭代某個設定的次數
for x in range(epochs):
counter = 0
for img in tr:
if (counter > samples):
break
if (counter % 1000 == 0):
print( "Epoch %d : Trained %d / %d " % (x, counter, samples))
counter += 1
data, digit = img
ann.train(np.array([data.ravel()], dtype=np.float32), cv2.ml.ROW_SAMPLE, np.array([digit.ravel()], dtype=np.float32))
print( "Epoch %d complete" % x)
return ann, test

#封裝ann的test
def test( ann, test_data):
sample = np.array(test_data[ 0][ 0].ravel(), dtype=np.float32).reshape( 28, 28)
cv2.imshow( "sample", sample)
cv2.waitKey()
print (ann.predict(np.array([test_data[ 0][ 0].ravel()], dtype=np.float32)))

#封裝ann的predict
def predict( ann, sample):
resized = sample.copy()
rows, cols = resized.shape
if (rows != 28 or cols != 28) and rows * cols > 0:
resized = cv2.resize(resized, ( 28, 28), interpolation = cv2.INTER_LINEAR)
return ann.predict(np.array([resized.ravel()], dtype=np.float32))

"""
使用方法::
ann, test_data = train(create_ANN())
test(ann, test_data)
"""




其中:
#讀取mnist.pkl.gz數據
def load_data():
mnist = gzip.open( 'D:/dl4cv/datesets/mnist.pkl.gz', 'rb')
#訓練集,校驗集和測試集 注意 pickle.load(mnist,encoding="bytes")
training_data, classification_data, test_data = pickle.load(mnist, encoding= "bytes")
mnist.close()
return (training_data, classification_data, test_data)

Load_data()主要作用是解壓數據集,然后從數據集中把數據取出來.
mnist.pkl.gz:是一個由兩個元素構成的元組.
其中一個元素是測試圖片集合,是一個50000*784的numpy ndarray(其中50000行就是數據的數量,784列就是一個數據的維度(這里是像素)).
第二個元素就是一個測試圖片的標簽集.是一個50000*1的numpy ndarray.其中指明了每行是一個什么數字…通俗的來說就是這個樣子: 
這里寫圖片描述

validation_data和test_data的結構和上面的training_data是一樣的,只是數量(元素的行數)不一樣.這兩個是10000行.


def wrap_data():
tr_d, va_d, te_d = load_data()
training_inputs = [np.reshape(x, ( 784, 1)) for x in tr_d[ 0]]
training_results = [vectorized_result(y) for y in tr_d[ 1]]
training_data = zip(training_inputs, training_results)

validation_inputs = [np.reshape(x, ( 784, 1)) for x in va_d[ 0]]
validation_data = zip(validation_inputs, va_d[ 1])

test_inputs = [np.reshape(x, ( 784, 1)) for x in te_d[ 0]]
test_data = zip(test_inputs, te_d[ 1])
#訓練集,校驗集和測試集,只有訓練集是有results的,也就是由監督的
return (training_data, validation_data, test_data)


load_data返回的格式雖然很漂亮,在load_data的基礎上面使用 wrap_data函數來進行變換,使其更加適合我們的神經網絡訓練.
以訓練集的變換為例。 對於training_inputs和 training_labels 來說,就是把之前的返回的training_data[0]的所有例子都放到了一個列表中:
這里寫圖片描述
這里寫圖片描述

training_data為zip函數組合,那么training_data為一個列表,其中每個元素是一個元組,二元組又有一個training_inputs和一個training_labels的元素組合而成 
 








免責聲明!

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



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