网络程序设计学习总结

2017-01-04  本文已影响0人  陌石路

学号:SA16225140
姓名:李豪俊


前言:
为期7周的“网络程序设计”课程即将告一段落,在此提笔写下对于这门课的总结。很遗憾这门课程的时间太短暂,个人感觉还有很多需要学习了解的地方,特别希望自己能够完整地研究透彻课程项目中所涉及地这些技术——对于基础薄弱的我来说有点眼睛不够用了。

对于这一学期的课程,老师将研究方向转向了神经网络方向。这令我有些措手不及,选课之初以为是关于网络开发的学习研究,得知研究方向的变化其实压力很大,但是也很兴奋,希望能够学到新的知识去丰富自己。本科的时候周围一些同学在进行机器学习相关实验的时候,一直就很好奇,也有些畏惧,感觉艰涩难懂。可以说,学习这门课之前,神经网络对我而言是一个非常高深艰涩的学科。所幸在老师的带领下一点点入门,揭开了这个领域的神秘面纱,让我有勇气去探索发现更多不一样的知识,丰富自己的认知。

1 课程目标

基于深度学习神经网络等机器学习技术实现一个医学辅助诊断的专家系统原型,具体切入点为课程项目(项目地址):对血常规检验报告的OCR识别,深度学习与分析,进行诸如预测患者的年龄和性别等工作,研究其中可能存在的联系。通过该项目,学习机器学习的常见算法框架,重点分析神经网路,理解和掌握常用算法框架工具的使用。

2 神经网路

2.1 简介(摘自百度百科)

** 人工神经网络 **(Artificial Neural Networks,简写为ANNs)也简称为神经网络(NNs)或称作连接模型(Connection Model),它是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的。

** 人工神经网络 **:是一种应用类似于大脑神经突触联接的结构进行信息处理的数学模型。在工程与学术界也常直接简称为“神经网络”或类神经网络。

2.2 个人认识

提到神经网络,不可避免得说到机器学习。想必都已经听说,AlphaGo大战围棋高手,近日Master在野狐平台斩获50连胜,快棋击败了无数围棋高手,彰显出机器学习的巨大进步与实力。而神经网络作为机器学习一种应用相当广泛的算法,具有非常重要的意义。

对于当前机器学习发展如火如荼的现状,课程中引入神经网络是非常有远见和胆魄的,相当考验教学者与学生。在老师和同学一致努力下,一个复杂高难度高要求的项目在短短7周内从无到有,亲眼见证这一切我感到无比震惊与自豪。当然我们这门课扩展了不只是神经网络的学习,同学们分享了诸如决策树、支持向量积、贝叶斯分类、集成学习等等都让人大开眼界。

神经网络,其计算模型灵感来自动物的中枢神经系统(尤其是脑),并且被用于估计或可以依赖于大量的输入和一般的未知近似函数。在我看来,即是在模仿脑部神经元突触的形成过程,通过不断地刺激(模型训练),发现新的认识(形成模型)。神经网络的一个重要特性是它能够从环境中学习,并把学习的结果分布存储于网络的突触连接中。神经网络的学习是一个过程,在其所处环境的激励下,相继给网络输入一些样本模式,并按照一定的规则(学习算法)调整网络各层的权值矩阵,待网络各层权值都收敛到一定值,学习过程结束。然后我们就可以用生成的神经网络来对真实数据做分类。

如下图,最左一列节点是输入节点,最右列节点是输出节点,中间节点是隐藏节点。该图结构是分层的,隐藏的部分有时候也会分为多个隐藏层。使用的层数非常多就会变成我们平常说的深度学习了。

神经网络模型

3 项目实践

3.1 神经网络实现手写字符识别

这个课程项目的学习算是对于神经网络初步入门,借以了解OCR识别和神经网络的基本概念。在网页端200x200的画布上监听鼠标按下操作绘制图形,点击按钮,获得画布结果,将其抽象为一个20x20的矩阵,并转为1x400的向量作为输入层。

简单的三层模型

这里主要运用前馈神经网络,该结构中不存在回路。而有输出反馈给输入的神经网络称作递归神经网络(RNN)。在这个实验中,我们使用前馈神经网络中经典的BP神经网络来实现手写识别系统。在数据前向传播时候用sigmoid函数作为激发函数。反向传播是数据的训练关键,需要通过计算误差率然后系统根据误差改变网络的权值矩阵和偏置向量。

sigmoid 两种变换函数

详情见 实验楼课程
进一步的学习 反向传播神经网络极简入门

3.2 对血常规检验报告的OCR识别、深度学习与分析

3.2.1 环境配置

# 安装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 结构说明


p1是高斯模糊的参数,p2和p3是canny边缘检测的高低阈值,p4和p5是和筛选有关的乘数。
如果化验报告单放在桌子上时,有的边缘会稍微翘起,产生比较明显的阴影,这种阴影有可能被识别出来,导致定位失败。 解决的方法是调整p2和p3,来将阴影线筛选掉。但是如果将p2和p3调的比较高,就会导致其他图里的黑线也被筛选掉了。 参数的选择是一个问题。 在getinfo.default中设置的是一个较低的阈值,p2=70,p3=30,这个阈值不会屏蔽阴影线。 如果改为p2=70,p3=50则可以屏蔽,但是会导致其他图片识别困难。

就现在来看,得到较好结果的前提主要有三个
化验单尽量平整
图片中应该包含全部的三条黑线
图片尽量不要包含化验单的边缘,如果有的话,请尽量避开有阴影的边缘。

  1. filter函数 - 过滤掉不合格的或非报告图片
    返回img经过透视过后的PIL格式的Image对象,如果缓存中有PerspectivImg则直接使用,没有先进行透视 过滤失败则返回None @param filter参数
  2. autocut函数 - 将图片中性别、年龄、日期和各项目名称数据分别剪切出来
    用于剪切ImageFilter中的img成员,剪切之后临时图片保存在out_path, 如果剪切失败,返回-1,成功返回0 @num 剪切项目数 @param 剪切参数
    剪切出来的图片在BloodTestReportOCR/temp_pics/ 文件夹下
    函数输出为data0.jpg,data1.jpg……等一系列图片,分别是白细胞计数,中性粒细胞记数等的数值的图片。


3.2.3 项目演示

运行.png 上传图片.png 生成报告.png 预测年龄性别.png

3.2.4 项目要点(Code Review)

  1. Flask轻量级框架,便于快速开发
    运行方式简洁
app = Flask(__name__, static_url_path="")
if __name__ == '__main__':
        app.run(host=app.config['SERVER_HOST'], port=app.config['SERVER_PORT'])

HTTP(与 Web 应用会话的协议)有许多不同的访问URL方法。默认情况下,路由只回应GET请求,但是通过route()
装饰器传递methods
参数可以改变这个行为。

@app.route('/', methods=['GET', 'POST'])
def index():
        return redirect('/index.html')

关于Flask Restful参见这里

  1. MongoDB非关系型数据库
    操作简单快捷,为WEB应用提供可扩展的高性能数据存储解决方案
# 连接数据库,并获取数据库对象
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)
  1. Vue
    由大牛尤雨溪开发的JavaScript框架,目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件
    代码示例如下,数据绑定非常简洁
            <table id= "table_left" class="table table-inverse table-hover table-bordered">
                <thead>
                <tr>
                    <th> </th>
                    <th>检测项目</th>
                    <th>结果</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: {………………
  1. AJAX
    AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新
    示例代码如下
        $.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);
            }
        });
  1. CDN
    值得注意的是,我们开发的时候采用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" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">
    <!-- 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>

在我阅读代码,运行测试的时候发现一些载入异常,对于这样的数据源````在某些网络下无法正常加载,会发生功能不能如期运行的BUG,当前使用<script src="http://static.runoob.com/assets/vue/1.0.11/vue.min.js">替换,也可以考虑使用http://cdn.bootcss.com/bootstrap/数据源

# 载入图像,灰度化,开闭运算,描绘边缘
        
        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)
![Canny描绘边缘.jpg](https://img.haomeiwen.com/i4255156/d262ee72354be2e5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2 调用CV2模块的findContours提取矩形轮廓,筛选对角线大于阈值的轮廓

# 调用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])
提取轮廓.png

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

# 由三条线来确定表头的位置和表尾的位置
        line_upper, line_lower = findhead(line[2],line[1],line[0])
将轮廓变为线.png

4 透视变换

        #由于需要表格外的数据,所以变换区域需要再向上和向下延伸
        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)

        #使用透视变换将表格区域转换为一个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)
透视变换结果.jpg
    learning_rate = 0.005
    display_step = 100
    n_input = 22

    n_hidden_1_sex = 16
    n_hidden_2_sex = 8
    n_classes_sex = 2
  1. 建立模型
    '''
    建立性别模型
    '''
    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)
  1. 运行测试
    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 总结反思

近70人共同完成这一个项目,相当考验项目管理者的集成能力,这么一个庞大的开发团体,也非常需要软件工程经验对我们进行指导。幸而,孟宁老师坐镇中枢,集成管理整个项目,保证了项目的进展与成功。可以毫不夸张地说,缺少孟宁老师这样的项目管理者,我们几乎无法完成这门课的学习。

正是因为项目的复杂、高难度以及参与人数的庞大,项目的可扩展性相当高。我看到针对项目的每个环节,都有同学提出了各种各样不同的实现方式,没有说给出一个定式。一个开放的实践课程,让我大开眼界。例如,对于预测,同学们提出了CNN、RNN、决策树、向量积等等不一样的实现方式;在对于框架的选择上,也有各种不一样的偏好:Caffee、Tensorflow、Karas。可以说是,“八仙过海,各显神通”,让我见识到了周围同学出色的代码编程以及学习能力,实在佩服。

这门课程对于机器学习的知识面狩猎非常之广,个人零基础,一开始听大神们侃侃而谈就有点迷糊跟不上节奏,因此买了一本周志华老师的《机器学习》自己默默啃着,然而因为下学期5门课程的重压,精力实在有点捉襟见肘,直到现在还没有看完。但是,随着课程的学习,我也简单了解了很多关于机器学习的知识,极大地开拓了眼界,与人谈论机器学习时至少能够搭得上话了吧。(不知道是不是也算有所得了)

需要反省的是,自己做事比较磨蹭,每次有新的想法总会犹豫一下,又对于自己能力有点不自信,故而未曾pr,最后零贡献实在有点难看,希望这些缺点能够改正。

最后在检查的时候,对于页面改善提出了一点看法,根据老师的要求进行了修改,集成Bootstrap上传控件,提交了一个pr #270 完成对于Bootstrap上传插件的集成,总算也不是零贡献了。

上一篇 下一篇

猜你喜欢

热点阅读