1. 程式人生 > >網路程式設計結課總結——神經網路篇

網路程式設計結課總結——神經網路篇

前情提要:

為什麼深度學習逐漸成為熱潮?
隨著計算機運算速度的提高和硬體條件的不斷改善,神經網路和深度學習逐漸涉及到了生活中的方方面面,成為了當今的一個熱門的話題。
我作為初入程式設計的菜鳥,程式碼能力比較差,因為本科專業的原因,所以有一定的數學基礎,也因此最終選擇了孟寧老師的網路程式設計課程。
這學期的網路程式設計課程主要內容是,基於深度學習神經網路等機器學習技術實現一個醫學輔助診斷的專家系統原型,具體切入點為:為血常規檢驗報告的OCR識別,深度學習與分析,神經網路基本內容的介紹(PPT展示),以及一些常見神經網路工具和API庫的使用和比較分析。部落格的主要內容分成三部分來進行展示。分別是課程專案的整體 工作流程和程式碼功能展示,我在其中做的功勞和苦勞,以及課程結束後的收穫與感悟。因為經驗不足和第一次寫部落格,有不足和不明的地方,還希望大家和我一起討論可憐~
(為了更好的展示整體流程和設計思路,部落格裡的部分圖片來自網路和同學!!!侵權刪麼麼噠!!!)
部落格裡涉及的主要工具有Tensorflow/spss/octave,版本都更新至最新版。Python版本為2.7。3以上可能不能執行文章中涉及的部分程式碼。

課程專案整體展示

課程專案A1:神經網路實現手寫字元識別系統

因為是剛入門的專案,所以難度不是很大(程式碼菜鳥啪啪打臉),主要是在實驗樓進行了程式碼實現,整個流程實驗樓裡總結的非常完整,對於新手來說非常容易上手,很利於培養自信心(……),所以作為一隻小白我就帶著這種迷之自信入了坑。
但是其實說實話,我一開始對圖片的轉換以及其中的各種神經網路完全是大寫的懵逼,雖然在大神的幫助下成功的跑出了結果但是可以說完全沒有任何理解。
作為一隻菜鳥,這時候我甚至還不是很理解機器學習、神經網路、深度學習之間的區別…
這裡要強勢安利一波coursera的神經網路課程!

(答應我連結請自己谷歌好嘛)
在經歷幾周的學習以後,我發現手寫字元識別真的是神經網路裡非常基礎而且用途非常廣泛的練手例項。TensorFlow的入門教程、octave跑的第一個例項,以及各種教學課程上都會使用它來帶新手入門,所以在掙扎摸索了很久後,我終於用Octave自己跑出了手寫字元識別系統的程式碼。
參與測試的字串
↑這裡是參與測試的字串的圖片。他們都已經被預處理為20*20的格式,一共有5000個數據(隨機選擇了其中的100個進行輸出顯示),被傳入儲存為一個5000*400的矩陣,其中每一行代表了一個獨立的字元。
手寫字元識別系統本質上是一個多分類問題,為了更好的理解神經網路的優越性,這裡我選擇使用線性分類和神經網路兩種模式進行了手寫字元識別系統,在模型訓練較好的前提下,二者的精度分別在94和97左右,
對整個迭代過程進行了輸出展示

線性擬合:按照分類器的數量,用for迴圈進行了分別迭代,迭代次數為50次,並計算代價函式,最後為預測精度。

這裡是迭代部分的程式碼:

%    Set Initial theta
for c = 1:num_labels  
  initial_theta = zeros(n + 1, 1);
%     
%    Set options for fminunc
     options = optimset('GradObj', 'on', 'MaxIter', 50);
% 
%    Run fmincg to obtain the optimal theta
%    This function will return theta and the cost 
     [theta] =fmincg (@(t)(lrCostFunction(t, X, (y == c), lambda)),initial_theta, options);
      all_theta(c,:)=theta';
     fprinf('zheshi',theta);

神經網路:

這裡神經網路的引數是課程裡直接給出的,打包成了weight資料檔案,在執行的時候對預測環節加入了display部分,對每一個隨機抽出的影象進行了還原展示,將標籤和真實值對比。
預測部分的程式碼如下:

function p = predict(Theta1, Theta2, X)
%PREDICT Predict the label of an input given a trained neural network
%   p = PREDICT(Theta1, Theta2, X) outputs the predicted label of X given the
%   trained weights of a neural network (Theta1, Theta2)

% Useful values
m = size(X, 1);
num_labels = size(Theta2, 1);

% You need to return the following variables correctly 
p = zeros(size(X, 1), 1);

% ====================== YOUR CODE HERE ======================
% Instructions: Complete the following code to make predictions using
%               your learned neural network. You should set p to a 
%               vector containing labels between 1 to num_labels.
%
% Hint: The max function might come in useful. In particular, the max
%       function can also return the index of the max element, for more
%       information see 'help max'. If your examples are in rows, then, you
%       can use max(A, [], 2) to obtain the max for each row.
%

 X=[ones(m,1) X];
 a=sigmoid(X*Theta1');
 a=[ones(m,1) a ];
 b=sigmoid(a*Theta2');
 [B,BX]=max(b,[],2);
 p=BX;

經過這番的比對分析,令我對手寫字元識別的整個系統有了更深入的理解。

課程專案A2 血常規檢驗報告影象的OCR識別

這裡我們期待的設計效果是,能夠從一個完整的體檢報告上手工截圖文字和數字的圖片作為輸入進行OCR識別。對一個非標準的體檢報告圖能夠正確識別表格的區域, 如果圖片不能識別或者資料出現錯誤能夠return一個錯誤資訊,以使使用者能夠重新上傳圖片。
這裡主要的設計思路是,通過對錶格邊緣三道黑線的識別來進行定位,從而正確分辨表格區域。在實際操作中還利用投射原理,對非正常位置的體檢報告圖進行正置標準化調整。

這裡部落格內容參考了班裡同學的整理內容,因為感覺她的整理更簡單明瞭和全面~
附上原作者的部落格直達連結→點這裡點這裡

 view.py --Web 端上傳圖片到伺服器,存入mongodb並獲取oid。
 imageFilter.py --對影象透視裁剪和OCR進行了簡單的封裝,便於模組間的互動,規定適當的介面.是整個ocr中最重要的模組.
 classifier.py --用於判定裁剪矯正後的報告和裁剪出檢測專案的編號
 imgproc.py --將識別的影象進行處理二值化等操作,提高識別率

ocr主要使用了opencv2包。

1 對輸入的影象進行處理,採用Canny運算元描繪邊緣

img_sp = self.img.shape
ref_lenth = img_sp[0] * img_sp[1] * ref_lenth_multiplier
img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)
img_gb = cv2.GaussianBlur(img_gray, (gb_param, gb_param), 0)
closed = cv2.morphologyEx(img_gb, cv2.MORPH_CLOSE, kernel)
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel)
edges = cv2.Canny(opened, canny_param_lower , canny_param_upper)

2 呼叫CV2模組中findContours函式來提取矩形輪廓,設定閾值,並篩選對角線大於閾值的輪廓

這裡有點問題是,因為老師一開始只給了一張實物圖,所以在引數設定的時候,很多設定是有針對性的(比如資料種類以及三道橫線…),在普適性上還需要繼續改進。

contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

def getbox(i):
rect = cv2.minAreaRect(contours[i])
box = cv2.cv.BoxPoints(rect)
box = np.int0(box)
return box

def distance(box):
delta1 = box[0]-box[2]
delta2 = box[1]-box[3]
distance1 = np.dot(delta1,delta1)
distance2 = np.dot(delta2,delta2)
distance_avg = (distance1 + distance2) / 2
return distance_avg

# 篩選出對角線足夠大的幾個輪廓
found = []
for i in range(len(contours)):
box = getbox(i)
distance_arr = distance(box)
if distance_arr > ref_lenth:
found.append([i, box])

def getline(box):
if np.dot(box[1]-box[2],box[1]-box[2]) < np.dot(box[0]-box[1],box[0]-box[1]):
point1 = (box[1] + box[2]) / 2
point2 = (box[3] + box[0]) / 2
lenth = np.dot(point1-point2, point1-point2)
return point1, point2, lenth
else:
point1 = (box[0] + box[1]) / 2
point2 = (box[2] + box[3]) / 2
lenth = np.dot(point1-point2, point1-point2)
return point1, point2, lenth

def cmp(p1, p2):
delta = p1 - p2
distance = np.dot(delta, delta)
if distance < img_sp[0] * img_sp[1] * ref_close_multiplier:
return 1
else:
return 0

def linecmp(l1, l2):
f_point1 = l1[0]
f_point2 = l1[1]
f_lenth = l1[2]
b_point1 = l2[0]
b_point2 = l2[1]
b_lenth = l2[2]
if cmp(f_point1,b_point1) or cmp(f_point1,b_point2) or cmp(f_point2,b_point1) or cmp(f_point2,b_point2):
if f_lenth > b_lenth:
return 1
else:
return -1
else:
return 0

def deleteline(line, j):
lenth = len(line)
for i in range(lenth):
if line[i] is j:
del line[i]
return

3 把輪廓變成線,並去除不合適的線

# 比較最小外接矩形相鄰兩條邊的長短,以兩條短邊的中點作為線的兩端
line = []

for i in found:
box = i[1]
point1, point2, lenth = getline(box)
line.append([point1, point2, lenth])

# 把不合適的線刪去
if len(line)>3:
for i in line:
for j in line:
if i is not j:
rst = linecmp(i, j)
if rst > 0:
deleteline(line, j)
elif rst < 0:
deleteline(line, i)

#檢測出的線數量不對就返回-1跳出
if len(line) != 3:
print "it is not a is Report!,len(line) =",len(line)
return None

def distance_line(i, j):
dis1 = np.dot(i[0]-j[0], i[0]-j[0])
dis2 = np.dot(i[0]-j[1], i[0]-j[1])
dis3 = np.dot(i[1]-j[0], i[1]-j[0])
dis4 = np.dot(i[1]-j[1], i[1]-j[1])
return min(dis1, dis2, dis3, dis4)

def findhead(i, j, k):
dis = []
dis.append([distance_line(i, j), i, j])
dis.append([distance_line(j, k), j, k])
dis.append([distance_line(k, i), k, i])
dis.sort()
if dis[0][1] is dis[2][2]:
return dis[0][1], dis[2][1]
if dis[0][2] is dis[2][1]:
return dis[0][2], dis[2][2]

def cross(vector1, vector2):
return vector1[0]*vector2[1]-vector1[1]*vector2[0]

這裡主要是各位大牛的突出貢獻~運用了透視變換和一系列API,並根據實際使用時的情況進行了不斷的細節調整。菜鳥表示只能簡單理解程式碼,為了方便回顧這裡附上了程式碼,但是就不多做介紹了,期待大牛們的分享。

課程專案A3 根據血常規檢驗的各項資料預測年齡和性別

在能夠對血常規檢驗報告單進行資料化處理後,我們將利用血常規檢驗分析單中的各種資料進行資料分析,以期待發現這二十多項中的內在聯絡,並希望構建一種模型,能夠較為穩定和準確的預測不同患者的年齡和性別。
因為資料來源的不斷更新,我們主要進行了兩次模型構建,訓練樣本分別為2000個和8500個左右,並進行了一系列資料相關性的基礎分析。
這裡我們主要使用的工具為TensorFlow,我還額外使用了spss對資料進行了一些基本處理和相關性分析,根據得出來的結論,調整模型中各項資料的比重和神經網路的權重,現在通過血常規的檢驗資料我們可以較為準確的判斷出患者的性別,準確率可以達到百分之八十以上。但是年齡即使經過分箱後,也只有百分之三十左右的預測準確率。
最後,按照老師的要求,我們將A3的模型整合到A2的OCR中,完成了一個完整的web系統。這裡有很多大牛進行了前段和後端的貢獻,作為一隻菜雞隻能默默的膜拜並進行了閱讀…大家跑出來的結果大同小異,就不進行細緻的解說了,依舊期待大牛分享程式碼經驗。
↓以下是部分程式碼和執行時候的截圖。
初始頁面
選擇圖片上傳
生成OCR資料單
進行性別和年齡預測
(這裡參考了田奇同學的部落格內容,具體細節分析請戳部落格正文

我在其中做出的功勞和苦勞

(以下是王婆賣瓜環節,貢獻微薄請各位大神輕拍~)

機器學習基礎知識相關的PPT製作與展示

在這個部分裡,我主要講解了機器學習中的隨機森林部分知識介紹,具體的PPT已經上傳到了課程後面的附件裡。
隨機森林PPT截圖
隨機森林是一種比較新的機器學習模型。經典的機器學習模型是神經網路,有半個多世紀的歷史了。神經網路預測精確,但是計算量很大。上世紀八十年代Breiman等人發明分類樹的演算法(Breiman et al. 1984),通過反覆二分資料進行分類或迴歸,計算量大大降低。2001年Breiman把分類樹組合成隨機森林(Breiman 2001a),即在變數(列)的使用和資料(行)的使用上進行隨機化,生成很多分類樹,再彙總分類樹的結果。隨機森林在運算量沒有顯著提高的前提下提高了預測精度。隨機森林對多元共線性不敏感,結果對缺失資料和非平衡的資料比較穩健,可以很好地預測多達幾千個解釋變數的作用(Breiman 2001b),被譽為當前最好的演算法之一(Iverson et al. 2008)。我主要講解了什麼是隨機森林——隨機森林的基本概念,隨機森林的一些經典演算法,和隨機森林擅長解決的一些問題和構建隨機森林模型時候會遇到的一些細節問題的解決。

在後面的實際操作環節,因為資料量的不足,所以神經網路並沒有特別突出的顯示出了它的優越性,而因為資料缺失和各種現實條件限制,也有很多同學選擇了在分類問題中多次使用隨機森林的模型,並取得了較好的預測效果,這也證明了隨機森林在具體用例上的實用性和普適性。

血常規檢驗的各項資料預測年齡和性別中的資料處理部分

從現實生活中轉換過來的資料,常常存在資料缺失,噪聲大,特徵值多,相關性高等缺點。在進行神經網路或者是利用線性擬合訓練模型的時候,在引數一定的情況下,對資料進行一定程度的初處理是一種非常通用,且便捷提高模型準確度的方法。
在實際專案中,預測的準確度很長時間一直卡在一個較低的水平無法提升,且前後兩次資料來源的不同導致了特徵值有所變化,所以進行資料的初處理是十分重要的。
這裡強烈安利一發SPSS
因為對程式碼不是很熟悉,所以我這裡選擇了一個簡單快捷的工具來實現資料的初處理。其中包括了:
(如果對公式比較煩躁的請直接看黑體字標題就好~)

標準化(對缺失資料進行刪除和補全)

這裡寫圖片描述

正規化

這裡寫圖片描述

歸一化(將資料對映到一個相同或者近似的區間裡,從而使得模型的引數範圍更相似,模型更穩健)

這裡寫圖片描述

降維和相關性分析(進行特徵值的選擇和模型的簡化)

這裡提供我根據老師給出的資料做出的一些和年齡和性別預測有關的相關性分析截圖:
這裡寫圖片描述
模型摘要
訓練 平方和錯誤 241.119
不正確預測數百分比 23.0%
已使用中止規則 1 連續步驟(含錯誤縮減)a
訓練時間 0:00:00.51
Testing 平方和錯誤 123.145
不正確預測數百分比 26.9%
應變數:sex
a. 錯誤計算是以測試樣本為基礎。
↑ 預測精度,年齡的因為預測度不高就不貼出來了。

課程結束後的收穫與感悟

這裡我想分為兩部分進行敘述,一部分是根據自己的使用經歷對部落格中提及的各種工具的總結和推薦,另一部分是課程中思想上的一些變化和實質上的收穫。

程式碼小白對一系列工具的評價~

作為一隻程式碼小白,我大概唯一的優勢是因為偷懶和懵逼嘗試了比大牛們更多的工具…所以也多多少少另闢蹊徑的做出了一點貢獻。正如angrew在機器學習 這門課程中所提到的,對不同的問題,選擇不同的工具來進行處理,會讓你的整個實踐過程事半功倍。隨著神經網路的快速發展和IT行業涉及生活方方面面的深入,越來越多利於開發者的軟體在逐步被開發出來,他們各有側重點,適用於不同的環境和使用者。這裡想要簡單的談一談對我在整個專案中使用工具的感受和推薦。

正如前情提要中說過的,我主要使用了TensorFlow、spss、octave三種工具。其中TensorFlow是我第一次嘗試使用,第一感受是十分不好上手,對待新手和程式碼小白十分不友好,剛開始使用的時候真的是大寫的懵逼。但是它在神經網路的學習和研究中使用非常廣泛,也是我們這門課程中使用人數最多的工具之一,它具有比較良好的受眾基礎以及各種較為簡單被呼叫的API。現在網路上它的課程和教程也非常多,所以以後我還是會選擇深入學習下去,進一步加深對這個工具的理解和使用。

SPSS和SAS都比較多的被用在資料分析領域,其中SAS更專業化,但是同理也更大(大概25G左右…),大家可以根據自己的需求酌情選擇。SPSS對使用者比較友好,使用起來非常簡單,進行主成分分析,資料標準化,相關性分析,模型視覺化等非常的方便快捷。我個人的使用經驗是,在進行程式碼訓練之前,跑一下相關性分析來對模型的預測效果有一個比較全域性的概念,以及進行簡單的降維處理是十分有用的。同時,spss也可以進行簡單的線性擬合、神經網路構建。可以說在資料量不大的前提下,我們完全可以不用一行程式碼,跑出一個高效的神經網路~缺點大概就是,黑箱操作過多,對程式設計師們來說,可調整的範圍比較少,對某些單一的功能或者較大的資料可能不友好。

octave是MATLAB的簡化版,所以有著和它極為相似的特點:程式碼簡單,內部函式功能齊全,對功能的原理要求比程式碼本身更高。它和MATLAB比起來最大的優勢就是小。。。大概只有幾百m,MATLAB一般可以很輕鬆的上5G。這個工具我是看了Coursera上的安利才使用的,只能說使用者體驗非常的好!比起繁瑣的程式碼debug,它支援更多的函式,且函式功能更為完善,也因此,它更關注對演算法本身的理解而不是程式碼中的一些細節錯誤(雖然這些也很重要,但是對於剛開始學習的我們來說他們並不是最主要的)。我們可以選擇在octave中實現我們的演算法思路和不同方法之間的對比模擬,然後選擇最優的方法來移植到TensorFlow和Python中,這被證明是十分簡潔有效的。

課程相關的一些碎碎念~

作為一個初學者。也許一開始的時候懵逼而懵懂,但是在這門課將要結束的今天回頭看,我會發現孟寧老師對課程的設計和安排是十分合理的。從一開始神經網路基礎知識的PPT展示,讓我們對神經網路的一些基本概念和演算法有了認識,到後面轉化為真正的、可以執行的一個完整的實踐專案。從一開始什麼都不懂完全照搬實驗樓的程式碼,到後面會利用自己的優勢對專案提供一些微薄的幫助,對不同領域的知識有了更深的知識。這裡面蘊含著將近70個同學一個學期共同的努力,同學和老師互相討論, 共同進步。其中大家的熱心、好學和勤奮給我留下了深刻的印象。雖然我還是一隻菜雞,但是我至少目睹了大牛們的風采~
在整個課程期間,我跟隨著著專案的發展,也在不斷的擴充套件著自己的知識寬度,孟寧老師的一句話很有道理:“專案是強大的驅動力”。
神經網路這個方向,是一個日新月異、知識不斷更替的領域,也許現在的我只是簡單的窺見了一絲它的神奇與神祕,站在大神的肩膀上看到了一些東西真正的轉化為了有用的、可以看見的實踐成果。但是我相信,在老師的引導下、在同學的幫助下,我會走的更長更遠。