學號:SA16225140
姓名:李豪俊
前言:
為期7周的“網(wǎng)絡程序設計”課程即將告一段落,在此提筆寫下對于這門課的總結(jié)。很遺憾這門課程的時間太短暫,個人感覺還有很多需要學習了解的地方,特別希望自己能夠完整地研究透徹課程項目中所涉及地這些技術——對于基礎薄弱的我來說有點眼睛不夠用了。
對于這一學期的課程,老師將研究方向轉(zhuǎn)向了神經(jīng)網(wǎng)絡方向。這令我有些措手不及,選課之初以為是關于網(wǎng)絡開發(fā)的學習研究,得知研究方向的變化其實壓力很大,但是也很興奮,希望能夠?qū)W到新的知識去豐富自己。本科的時候周圍一些同學在進行機器學習相關實驗的時候,一直就很好奇,也有些畏懼,感覺艱澀難懂。可以說,學習這門課之前,神經(jīng)網(wǎng)絡對我而言是一個非常高深艱澀的學科。所幸在老師的帶領下一點點入門,揭開了這個領域的神秘面紗,讓我有勇氣去探索發(fā)現(xiàn)更多不一樣的知識,豐富自己的認知。
1 課程目標
基于深度學習神經(jīng)網(wǎng)絡等機器學習技術實現(xiàn)一個醫(yī)學輔助診斷的專家系統(tǒng)原型,具體切入點為課程項目(項目地址):對血常規(guī)檢驗報告的OCR識別,深度學習與分析,進行諸如預測患者的年齡和性別等工作,研究其中可能存在的聯(lián)系。通過該項目,學習機器學習的常見算法框架,重點分析神經(jīng)網(wǎng)路,理解和掌握常用算法框架工具的使用。
2 神經(jīng)網(wǎng)路
2.1 簡介(摘自百度百科)
** 人工神經(jīng)網(wǎng)絡 **(Artificial Neural Networks,簡寫為ANNs)也簡稱為神經(jīng)網(wǎng)絡(NNs)或稱作連接模型(Connection Model),它是一種模仿動物神經(jīng)網(wǎng)絡行為特征,進行分布式并行信息處理的算法數(shù)學模型。這種網(wǎng)絡依靠系統(tǒng)的復雜程度,通過調(diào)整內(nèi)部大量節(jié)點之間相互連接的關系,從而達到處理信息的目的。
** 人工神經(jīng)網(wǎng)絡 **:是一種應用類似于大腦神經(jīng)突觸聯(lián)接的結(jié)構(gòu)進行信息處理的數(shù)學模型。在工程與學術界也常直接簡稱為“神經(jīng)網(wǎng)絡”或類神經(jīng)網(wǎng)絡。
2.2 個人認識
提到神經(jīng)網(wǎng)絡,不可避免得說到機器學習。想必都已經(jīng)聽說,AlphaGo大戰(zhàn)圍棋高手,近日Master在野狐平臺斬獲50連勝,快棋擊敗了無數(shù)圍棋高手,彰顯出機器學習的巨大進步與實力。而神經(jīng)網(wǎng)絡作為機器學習一種應用相當廣泛的算法,具有非常重要的意義。
對于當前機器學習發(fā)展如火如荼的現(xiàn)狀,課程中引入神經(jīng)網(wǎng)絡是非常有遠見和膽魄的,相當考驗教學者與學生。在老師和同學一致努力下,一個復雜高難度高要求的項目在短短7周內(nèi)從無到有,親眼見證這一切我感到無比震驚與自豪。當然我們這門課擴展了不只是神經(jīng)網(wǎng)絡的學習,同學們分享了諸如決策樹、支持向量積、貝葉斯分類、集成學習等等都讓人大開眼界。
神經(jīng)網(wǎng)絡,其計算模型靈感來自動物的中樞神經(jīng)系統(tǒng)(尤其是腦),并且被用于估計或可以依賴于大量的輸入和一般的未知近似函數(shù)。在我看來,即是在模仿腦部神經(jīng)元突觸的形成過程,通過不斷地刺激(模型訓練),發(fā)現(xiàn)新的認識(形成模型)。神經(jīng)網(wǎng)絡的一個重要特性是它能夠從環(huán)境中學習,并把學習的結(jié)果分布存儲于網(wǎng)絡的突觸連接中。神經(jīng)網(wǎng)絡的學習是一個過程,在其所處環(huán)境的激勵下,相繼給網(wǎng)絡輸入一些樣本模式,并按照一定的規(guī)則(學習算法)調(diào)整網(wǎng)絡各層的權(quán)值矩陣,待網(wǎng)絡各層權(quán)值都收斂到一定值,學習過程結(jié)束。然后我們就可以用生成的神經(jīng)網(wǎng)絡來對真實數(shù)據(jù)做分類。
如下圖,最左一列節(jié)點是輸入節(jié)點,最右列節(jié)點是輸出節(jié)點,中間節(jié)點是隱藏節(jié)點。該圖結(jié)構(gòu)是分層的,隱藏的部分有時候也會分為多個隱藏層。使用的層數(shù)非常多就會變成我們平常說的深度學習了。

3 項目實踐
3.1 神經(jīng)網(wǎng)絡實現(xiàn)手寫字符識別
這個課程項目的學習算是對于神經(jīng)網(wǎng)絡初步入門,借以了解OCR識別和神經(jīng)網(wǎng)絡的基本概念。在網(wǎng)頁端200x200的畫布上監(jiān)聽鼠標按下操作繪制圖形,點擊按鈕,獲得畫布結(jié)果,將其抽象為一個20x20的矩陣,并轉(zhuǎn)為1x400的向量作為輸入層。

這里主要運用前饋神經(jīng)網(wǎng)絡,該結(jié)構(gòu)中不存在回路。而有輸出反饋給輸入的神經(jīng)網(wǎng)絡稱作遞歸神經(jīng)網(wǎng)絡(RNN)。在這個實驗中,我們使用前饋神經(jīng)網(wǎng)絡中經(jīng)典的BP神經(jīng)網(wǎng)絡來實現(xiàn)手寫識別系統(tǒng)。在數(shù)據(jù)前向傳播時候用sigmoid函數(shù)作為激發(fā)函數(shù)。反向傳播是數(shù)據(jù)的訓練關鍵,需要通過計算誤差率然后系統(tǒng)根據(jù)誤差改變網(wǎng)絡的權(quán)值矩陣和偏置向量。

詳情見 實驗樓課程
進一步的學習 反向傳播神經(jīng)網(wǎng)絡極簡入門
3.2 對血常規(guī)檢驗報告的OCR識別、深度學習與分析
3.2.1 環(huán)境配置
- 操作系統(tǒng)
優(yōu)先選擇 Ubuntu 14.04 - 開發(fā)配置
# 安裝numpy,
sudo apt-get install python-numpy # http://www.numpy.org/
# 安裝opencv
sudo apt-get install python-opencv # http://opencv.org/
# 安裝OCR和預處理相關依賴
sudo apt-get install tesseract-ocr
sudo pip install pytesseract
sudo apt-get install python-tk
sudo pip install pillow
# 安裝Flask框架
sudo pip install Flask
# 安裝mongoDB(如果找不到可以先sudo apt-get update)
sudo apt-get install mongodb
sudo service mongodb started
sudo pip install pymongo
# 安裝tensorflow,keras框架
sudo pip install tensorflow
sudo pip install keras
- 運行
cd BloodTestReportOCR
python view.py # upload圖像,在瀏覽器打開http://yourip:8080
3.2.2 結(jié)構(gòu)說明
- view.py
使用Flask(輕量級 Web 應用框架),搭配MongoDB,快速構(gòu)建Web應用
只通過一個view.py即可處理對于該項目所需提供響應的Web請求
Web 端上傳圖片到服務器,通過ImageFilter進行圖片優(yōu)化處理并切割,進行OCR識別,如果是可以識別的圖片文件,存入mongodb并返回其fid
- imageFilter.py
對圖像透視裁剪和OCR識別進行了簡單的封裝,以便于模塊間的交互,規(guī)定適當?shù)慕涌?- ocr函數(shù) - 模塊主函數(shù)返回識別數(shù)據(jù)
用于對img進行ocr識別,他會先進行剪切,之后進一步做ocr識別,返回一個json對象 如果剪切失敗,則返回None @num 規(guī)定剪切項目數(shù) - perspect函數(shù)做 - 初步的矯正圖片
用于透視image,他會緩存一個透視后的opencv numpy矩陣,并返回該矩陣 透視失敗,則會返回None,并打印不是報告 @param 透視參數(shù)
關于param: 參數(shù)的形式為[p1, p2, p3 ,p4 ,p5]。 p1,p2,p3,p4,p5都是整型,其中p1必須是奇數(shù)。
- ocr函數(shù) - 模塊主函數(shù)返回識別數(shù)據(jù)
p1是高斯模糊的參數(shù),p2和p3是canny邊緣檢測的高低閾值,p4和p5是和篩選有關的乘數(shù)。
如果化驗報告單放在桌子上時,有的邊緣會稍微翹起,產(chǎn)生比較明顯的陰影,這種陰影有可能被識別出來,導致定位失敗。 解決的方法是調(diào)整p2和p3,來將陰影線篩選掉。但是如果將p2和p3調(diào)的比較高,就會導致其他圖里的黑線也被篩選掉了。 參數(shù)的選擇是一個問題。 在getinfo.default中設置的是一個較低的閾值,p2=70,p3=30,這個閾值不會屏蔽陰影線。 如果改為p2=70,p3=50則可以屏蔽,但是會導致其他圖片識別困難。
就現(xiàn)在來看,得到較好結(jié)果的前提主要有三個
化驗單盡量平整
圖片中應該包含全部的三條黑線
圖片盡量不要包含化驗單的邊緣,如果有的話,請盡量避開有陰影的邊緣。
- filter函數(shù) - 過濾掉不合格的或非報告圖片
返回img經(jīng)過透視過后的PIL格式的Image對象,如果緩存中有PerspectivImg則直接使用,沒有先進行透視 過濾失敗則返回None @param filter參數(shù) - autocut函數(shù) - 將圖片中性別、年齡、日期和各項目名稱數(shù)據(jù)分別剪切出來
用于剪切ImageFilter中的img成員,剪切之后臨時圖片保存在out_path, 如果剪切失敗,返回-1,成功返回0 @num 剪切項目數(shù) @param 剪切參數(shù)
剪切出來的圖片在BloodTestReportOCR/temp_pics/ 文件夾下
函數(shù)輸出為data0.jpg,data1.jpg……等一系列圖片,分別是白細胞計數(shù),中性粒細胞記數(shù)等的數(shù)值的圖片。
- imgproc.py
將識別的圖像進行處理二值化等操作,提高識別率(包括對中文和數(shù)字的處理)
依次進行了,灰度化,二值化,腐蝕,放大,腐蝕,膨脹,直方圖均衡化,中值濾波去噪點等操作,返回一個經(jīng)過處理的圖片
- tf_predict.py
使用Tensorflow框架進行年齡、性別預測 - caffe_predict.py
使用Caffe框架進行年齡、性別預測
- classifier.py
使用pHash.py判定裁剪矯正后的報告是否為血常規(guī)檢驗報告,判定裁剪出檢測項目的編號 - pHash.py
利用離散余弦變換(DCT)獲取圖片的低頻成分,與已有的一張已經(jīng)裁剪好的標準圖片做對比,如果相似度>60%則接受,判定為血常規(guī)檢測報告 - config.py
用于設定數(shù)據(jù),當前包括:可識別圖片的類型,數(shù)據(jù)庫的地址與端口號,服務器配置地址與端口號,F(xiàn)lask框架Debug模式開關
3.2.3 項目演示




3.2.4 項目要點(Code Review)
- 網(wǎng)頁配置
- Flask輕量級框架,便于快速開發(fā)
運行方式簡潔
app = Flask(__name__, static_url_path="")
if __name__ == '__main__':
app.run(host=app.config['SERVER_HOST'], port=app.config['SERVER_PORT'])
HTTP(與 Web 應用會話的協(xié)議)有許多不同的訪問URL方法。默認情況下,路由只回應GET請求,但是通過route()
裝飾器傳遞methods
參數(shù)可以改變這個行為。
@app.route('/', methods=['GET', 'POST'])
def index():
return redirect('/index.html')
關于Flask Restful參見這里
- MongoDB非關系型數(shù)據(jù)庫
操作簡單快捷,為WEB應用提供可擴展的高性能數(shù)據(jù)存儲解決方案
# 連接數(shù)據(jù)庫,并獲取數(shù)據(jù)庫對象
db = MongoClient(app.config['DB_HOST'], app.config['DB_PORT']).test
c = dict(report_data=report_data,
content=bson.binary.Binary(content.getvalue()),
filename=secure_filename(f.name),
mime=mime)
db.files.save(c)
- Vue
由大牛尤雨溪開發(fā)的JavaScript框架,目標是通過盡可能簡單的 API 實現(xiàn)響應的數(shù)據(jù)綁定和組合的視圖組件
代碼示例如下,數(shù)據(jù)綁定非常簡潔
<table id= "table_left" class="table table-inverse table-hover table-bordered">
<thead>
<tr>
<th> </th>
<th>檢測項目</th>
<th>結(jié)果</th>
<th>參考范圍</th>
<th>單位</th>
</tr>
</thead>
<tbody>
<tr v-for="item in report_items_left">
<td>{{ item.count }}</td>
<td>{{ item.name }}</td>
<td>
<input type="text" v-model="item.value" class="form-control" placeholder="檢測值" />
</td>
<td>{{ item.range }}</td>
<td>{{ item.unit }}</td>
</tr>
</tbody>
</table>
新建Vue對象,在methods自定義方法
var report = new Vue({
el: '#report',
data: {
report_items_left: new Array(),
report_items_right: new Array(),
},
methods: {………………
- AJAX
AJAX 可以使網(wǎng)頁實現(xiàn)異步更新。這意味著可以在不重新加載整個網(wǎng)頁的情況下,對網(wǎng)頁的某部分進行更新
示例代碼如下
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: new FormData(this),
processData: false,
contentType: false
}).done(function(data) {
//console.log(data.templates);
if(data.error == 1)
{
alert("圖片不合格!");
}else
{
$("#filtered-image").empty().append(data.templates);
}
});
- CDN
值得注意的是,我們開發(fā)的時候采用CDN加載外部css,js等資源文件
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BloodTestOCR</title>
<!-- Jquey load frist-->
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js" type="text/javascript"></script>
<!-- Bootstrap -->
<link rel="stylesheet" >
<!-- bootstrap.js below is needed if you wish to zoom and view file content
in a larger detailed modal dialog -->
<!-- https://unpkg.com/vue/dist/vue.js -->
<script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="http://static.runoob.com/assets/vue/1.0.11/vue.min.js"></script>
</head>
在我閱讀代碼,運行測試的時候發(fā)現(xiàn)一些載入異常,對于這樣的數(shù)據(jù)源````在某些網(wǎng)絡下無法正常加載,會發(fā)生功能不能如期運行的BUG,當前使用<script src="http://static.runoob.com/assets/vue/1.0.11/vue.min.js">替換,也可以考慮使用http://cdn.bootcss.com/bootstrap/數(shù)據(jù)源
- 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 調(diào)用CV2模塊的findContours提取矩形輪廓,篩選對角線大于閾值的輪廓
# 調(diào)用findContours提取輪廓
contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 篩選出對角線足夠大的幾個輪廓
found = []
for i in range(len(contours)):
box = getbox(i)
distance_arr = distance(box)
if distance_arr > ref_lenth:
found.append([i, box])

3 將輪廓變成線,并去除不合適的線
# 將輪廓變?yōu)榫€
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)
#檢測出的線數(shù)量不對就返回-1跳出
if len(line) != 3:
print "it is not a is Report!,len(line) =",len(line)
return None
# 由三條線來確定表頭的位置和表尾的位置
line_upper, line_lower = findhead(line[2],line[1],line[0])

4 透視變換
#由于需要表格外的數(shù)據(jù),所以變換區(qū)域需要再向上和向下延伸
left_axis = line_lower[0] - line_upper[0]
right_axis = line_lower[1] - line_upper[1]
line_upper[0] = line_upper[0] - left_axis * 2 / 15
line_upper[1] = line_upper[1] - right_axis * 2 / 15
line_lower[0] = line_lower[0] + left_axis * 2 / 15
line_lower[1] = line_lower[1] + right_axis * 2 / 15
#設定透視變換的矩陣
points = np.array([[line_upper[0][0], line_upper[0][1]], [line_upper[1][0], line_upper[1][1]],
[line_lower[0][0], line_lower[0][1]], [line_lower[1][0], line_lower[1][1]]],np.float32)
standard = np.array([[0,0], [1000, 0], [0, 760], [1000, 760]],np.float32)
#使用透視變換將表格區(qū)域轉(zhuǎn)換為一個1000*760的圖
PerspectiveMatrix = cv2.getPerspectiveTransform(points,standard)
self.PerspectiveImg = cv2.warpPerspective(self.img, PerspectiveMatrix, (1000, 760))
#輸出透視變換后的圖片
cv2.imwrite(self.output_path + 'region.jpg', self.PerspectiveImg)

- 數(shù)據(jù)分析(Tensorflow預測性別為例)
- 設置參數(shù)
learning_rate = 0.005
display_step = 100
n_input = 22
n_hidden_1_sex = 16
n_hidden_2_sex = 8
n_classes_sex = 2
- 建立模型
'''
建立性別模型
'''
x_sex = tf.placeholder("float", [None, n_input])
y_sex = tf.placeholder("float", [None, n_classes_sex])
def multilayer_perceptron_sex(x_sex, weights_sex, biases_sex):
# Hidden layer with RELU activation
layer_1 = tf.add(tf.matmul(x_sex, weights_sex['h1']), biases_sex['b1'])
layer_1 = tf.nn.relu(layer_1)
# Hidden layer with RELU activation
layer_2 = tf.add(tf.matmul(layer_1, weights_sex['h2']), biases_sex['b2'])
layer_2 = tf.nn.relu(layer_2)
# Output layer with linear activation
out_layer = tf.matmul(layer_2, weights_sex['out']) + biases_sex['out']
return out_layer
weights_sex = {
'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1_sex])),
'h2': tf.Variable(tf.random_normal([n_hidden_1_sex, n_hidden_2_sex])),
'out': tf.Variable(tf.random_normal([n_hidden_2_sex, n_classes_sex]))
}
biases_sex = {
'b1': tf.Variable(tf.random_normal([n_hidden_1_sex])),
'b2': tf.Variable(tf.random_normal([n_hidden_2_sex])),
'out': tf.Variable(tf.random_normal([n_classes_sex]))
}
pred_sex = multilayer_perceptron_sex(x_sex, weights_sex, biases_sex)
- 運行測試
saver = tf.train.Saver()
init = tf.global_variables_initializer()
with tf.Session() as sess:
saver.restore(sess, "./model.ckpt")
print ("load model success!")
p_sex = sess.run(pred_sex, feed_dict={x_sex: data_predict})
if p_sex[0][0] > p_sex[0][1]:
sex_result = 1
else:
sex_result = 0
return sex_result
4 總結(jié)反思
近70人共同完成這一個項目,相當考驗項目管理者的集成能力,這么一個龐大的開發(fā)團體,也非常需要軟件工程經(jīng)驗對我們進行指導。幸而,孟寧老師坐鎮(zhèn)中樞,集成管理整個項目,保證了項目的進展與成功??梢院敛豢鋸埖卣f,缺少孟寧老師這樣的項目管理者,我們幾乎無法完成這門課的學習。
正是因為項目的復雜、高難度以及參與人數(shù)的龐大,項目的可擴展性相當高。我看到針對項目的每個環(huán)節(jié),都有同學提出了各種各樣不同的實現(xiàn)方式,沒有說給出一個定式。一個開放的實踐課程,讓我大開眼界。例如,對于預測,同學們提出了CNN、RNN、決策樹、向量積等等不一樣的實現(xiàn)方式;在對于框架的選擇上,也有各種不一樣的偏好:Caffee、Tensorflow、Karas??梢哉f是,“八仙過海,各顯神通”,讓我見識到了周圍同學出色的代碼編程以及學習能力,實在佩服。
這門課程對于機器學習的知識面狩獵非常之廣,個人零基礎,一開始聽大神們侃侃而談就有點迷糊跟不上節(jié)奏,因此買了一本周志華老師的《機器學習》自己默默啃著,然而因為下學期5門課程的重壓,精力實在有點捉襟見肘,直到現(xiàn)在還沒有看完。但是,隨著課程的學習,我也簡單了解了很多關于機器學習的知識,極大地開拓了眼界,與人談論機器學習時至少能夠搭得上話了吧。(不知道是不是也算有所得了)
需要反省的是,自己做事比較磨蹭,每次有新的想法總會猶豫一下,又對于自己能力有點不自信,故而未曾pr,最后零貢獻實在有點難看,希望這些缺點能夠改正。
最后在檢查的時候,對于頁面改善提出了一點看法,根據(jù)老師的要求進行了修改,集成Bootstrap上傳控件,提交了一個pr #270 完成對于Bootstrap上傳插件的集成,總算也不是零貢獻了。