一、课程项目目标
搭建一个完整的web系统,功能上能识别上传血常规检验报告图片中的年龄、性别及检验的各项数据,并能通过神经网络等机器学习算法根据血常规检验的各项数据预测年龄和性别。
二、学习心得总结
孟宁老师的这门网络程序设计课程素材并没有沿用一些固有的成型的更多有练手性质的项目,而是很创新地选择当前热门潮流的机器学习方向,着手方向是很新的。更难得的是,面对日益重要的医疗行业,看准了当前辅助诊断系统还要更加完善智能的契机和需求,这个项目不仅是一次尝试,也是很具有实际意义的。
作为一个项目小白我在该课程中经历了一个项目从无到有的过程,充分体会到了这种开放资源、协作互动的模式,还有清晰的文档说明、严谨的代码复审等重要性,这种模式推进的项目完善远没有想象的简单,却实际来的高效。
我对这门课程的收获从原来对机器学习就简单想到聚类算法的片面认识,到现在认识了许多机器学习库,尤其对Spark中的mlib库进行了认真学习和运用,还学习了常用的算法:决策树、贝叶斯、随机森林、svm等。以及通过ocr识别学习了解了一些视觉库。总之通过图像、预测、web三个模块我学习了一个项目雏形的大概构造,也对机器学习不再感到很神秘,对另外一些如opencv库等也提起了兴趣。
学习流程A1神经网络实现手写字符识别系统,这个demo主要对深度学习有一个具象的认识,这个过程主要是配置环境,然后我开始学习一些python的基础语法;A2血常规检验报告的图像OCR识别,这一步对我而言跨的比较大,只是fetch最新版本后从有经验同学代码中了解了图像识别中根据图像本身特点及几何学做特征提取、识别、预处理的这种思路;A3根据血常规检验的各项数据预测年龄和性别,因为鼓励大家尝试不同的学习库看预测准确度的优劣,来自工程实践同学的推荐这一部分我投入很多精力学习了Spark的mlib库,并尝试了它提供的贝叶斯、决策树、二分类、随机深林等不同算法,虽然每种算法间核心代码改动不超过10行,但只有动手尝试过以后才能更好理解一些参数的意义和它们之间的区别。只有这一部分本来是我觉得有希望pr的,但因为可能Spark平台提供的封装确实简便,我对项目托管平台相关操作各种不熟,很快就已经有大神把各种spark下算法都上传了并有详尽的结果分析,再次感觉和别人的巨大差距。整个项目是以python为基础的,作为一个刚开始接触python的小白没有做出什么贡献,但从一开始接到任务的手足无措,到慢慢知道该从何下手,我从各路大神代码中偷偷学到了许多,兴趣是最好老师,希望从一次次蹒跚前进中逐渐锻炼成一个优秀的码农。
三、Spark下随机森林算法
随机森林算法是机器学习、计算机视觉等领域内应用极为广泛的一个算法,它不仅可以用来做分类,也可用来做回归即预测,随机森林机由多个决策树构成,相比于单个决策树算法,它分类、预测效果更好,不容易出现过度拟合的情况。
由多个决策树构成的森林,算法分类结果由这些决策树投票得到,决策树在生成的过程当中分别在行方向和列方向上添加随机过程,行方向上构建决策树时采用放回抽样(bootstraping)得到训练数据,列方向上采用无放回随机抽样得到特征子集,并据此得到其最优切分点,这便是随机森林算法的基本原理,如下图:
简言之随机森林算法能够很好地避免决策树的过度拟合问题,因为随机森林通过若干决策树进行投票来决定最终的预测或分类结果。同时为解决随机森林分布环境下的效率问题,随机森林在分布式环境下进行了优化。Spark 中的随机森林算法主要实现了三个优化策略:1、切分点抽样统计2、特征装箱(Binning)3、逐层训练(level-wise training)。
关于Spark下随机森林算法接口调用核心代码如下:
1 from __future__ import print_function 2 import json 3 import sys 4 import math 5 from pyspark import SparkContext 6 from pyspark.mllib.classification import NaiveBayes, NaiveBayesModel 7 from pyspark.mllib.util import MLUtils 8 #随机森林中关键的类是 org.apache.spark.mllib.tree.RandomForest、org.apache.spark.mllib.tree.model.RandomForestModel 这两个类,它们提供了随机森林具体的 trainClassifier 和 predict 函数。 9 10 class BloodTestReportbyNB: 11 12 def __init__(self, sc): 13 self.sc = sc 14 # 读取数据 15 print('Begin Load Data File!') 16 self.sexData = MLUtils.loadLabeledPoints(self.sc, "LabeledPointsdata_sex.txt") 17 self.ageData = MLUtils.loadLabeledPoints(self.sc, "LabeledPointsdata_age.txt") 18 print('Data File has been Loaded!') 19 self.predict_gender = "" 20 self.predict_age = "" 21 22 def predict(self): 23 sexTraining = self.sexData 24 ageTraining = self.ageData 25 #训练随机森林分类器,trainClassifier返回的是RandomForestModel对象 26 #分类数;categoricalFeaturesInfo为空,意味着所有的特征为连续型变量;树的个数;特征子集采样策略,auto 表示算法自主选取;纯度计算;树的最大层次;特征最大装箱数。具体我设置的是树=3,最大深度=4,最大叶子数=32,纯度计算方式:基尼系数;性别分类=2,年龄分类=1000(取值与纯度计算方式有关)。 27 sexModel = RandomForest.trainClassifier(sexTraining,numClasses=2, categoricalFeaturesInfo={}, 28 numTrees=3, featureSubsetStrategy="auto", 29 impurity='gini', maxDepth=4, maxBins=32) 30 ageModel = RandomForest.trainClassifier(ageTraining,numClasses=1000, categoricalFeaturesInfo={}, 31 numTrees=3, featureSubsetStrategy="auto", 32 impurity='gini', maxDepth=4, maxBins=32) 33 #读取预测数据 34 sexPredict = MLUtils.loadLabeledPoints(self.sc, "LabeledPointsdata_predict_gender.txt") 35 agePredict = MLUtils.loadLabeledPoints(self.sc, "LabeledPointsdata_predict_age.txt") 36 # 对数据进行预测 37 predict_genderCollect = sexPredict.map(lambda p: p.label).zip(sexModel.predict(sexTest.map(lambda x: x.features)))#导入rdd数据集,组成键值对 38 predict_ageCollect = agePredict.map(lambda p: p.label).zip(ageModel.predict(ageTest.map(lambda x: x.features))) 39 self.predict_gender = predict_genderCollect[0] 40 self.predict_age = predict_ageCollect[0] 41 if 0 == self.predict_gender: 42 self.predict_gender = "男" 43 if 1 == self.predict_gender: 44 self.predict_gender = "女" 45 print ('Predict Gender:', self.predict_gender) 46 print ('Predict Age:', self.predict_age) 47 predict_data = { 48 "age": self.predict_age, 49 "gender": self.predict_gender 50 } 51 json_data = json.dumps(predict_data, ensure_ascii=False) 52 print ('JSON data of predict_data:', json_data) 53 return json_data 54 if __name__ == "__main__": 55 sc = SparkContext(appName="BloodTestReportPythonNaiveBayesExample") 56 BloodTestReportbyNB = BloodTestReportbyNB(sc) 57 BloodTestReportbyNB.predict()
其中随机森林中关键的类是org.apache.spark.mllib.tree.RandomForest、org.apache.spark.mllib.tree.model.RandomForestModel这两个类,它们提供了随机森林具体的trainClassifier和predict函数。
另外随机森林模型函数的参数分别代表分类数;categoricalFeaturesInfo为空,意味着所有的特征为连续型变量;树的个数;特征子集采样策略,auto 表示算法自主选取;纯度计算;树的最大层次;特征最大装箱数。具体我设置的是树=3,最大深度=4,最大叶子数=32,纯度计算方式:基尼系数;性别分类=2,年龄分类=1000(取值与纯度计算方式有关)。
利用随机森林算法对性别预测的准确度可以达到71%,中途输出结果如图:
同时为了生成Spark能过识别的labeled point数据(格式如图,每行第一位为标签位,后面以空格隔开的为特征因子),与前端整合过程中要分别处理深度学习数据集data_all.csv,将数据集中的数据项与BloodTestReportOCR中血常规报告单的数据项保持一致,以及将JSON格式的预测数据转换为Spark可识别的LabeledPoint数据并分别存放于LabeledPointsdata_predict_age.txt和LabeledPointsdata_predict_gender.txt。实现代码分别如下:
1 #将JSON格式的血常规数据转换为Saprk可识别的LabeledPoint数据 2 def predict_dataFormat(report_data): 3 output1 = open('LabeledPointsdata_predict_age.txt', 'w') 4 output2 = open('LabeledPointsdata_predict_gender.txt', 'w') 5 outputline1 = str(report_data["profile"]["age"]) + "," 6 if report_data["profile"]["gender"] == "Man": 7 outputline2 = "0," 8 if report_data["profile"]["gender"] == "Woman": 9 outputline2 = "1," 10 for item in report_data["bloodtest"]: 11 if item["alias"] == "GRA" or item["alias"] == "EO" or item["alias"] == "GRA%" or item["alias"] == "EO%": 12 continue 13 else: 14 outputline1 += item["value"] + " " 15 outputline2 += item["value"] + " " 16 output1.write(outputline1) 17 output2.write(outputline2)
1 #生成LabeledPoints类型数据,格式如下: 2 #label,factor1 factor2 .... factorn 3 #第一列为类别标签,后面以空格隔开的为特征(因子) 4 import csv 5 reader = csv.reader(file('./data_set.csv', 'rb')) 6 output1 = open('LabeledPointsdata_age.txt', 'w') 7 output2 = open('LabeledPointsdata_sex.txt', 'w') 8 9 flag = 0 10 row = 0 11 12 for line in reader: 13 row = row + 1 14 if 1 == row: 15 continue 16 17 column = 0 18 for c in line: 19 column = column + 1 20 if 1 == column: 21 continue 22 if 2 == column: 23 if "男" == c: 24 outputline2 = "0," 25 else: 26 outputline2 = "1," 27 continue 28 if 3 == column: 29 outputline1 = c + "," 30 else: 31 if "--.--"==c: 32 flag = 1 33 break 34 else: 35 outputline1 += (c + " ") 36 outputline2 += (c + " ") 37 if 0 == flag: 38 outputline1 += '\n' 39 outputline2 += '\n' 40 else: 41 flag = 0 42 continue 43 print(column) 44 output1.write(outputline1) 45 output2.write(outputline2) 46 output1.close() 47 output2.close() 48 print('Format Successful!')
四、项目Demo
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 install mongodb # 如果提示no module name mongodb, 先执行sudo apt-get update sudo service mongodb started sudo pip install pymongo #安装JDK、Scala、Spark(略),并配置JDK和Spark相关的环境变量(sudo gedit /etc/profile,在文件最后添加如下配置) export JAVA_HOME=/opt/jdk1.8.0_45 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin:$PATH export SCALA_HOME=/opt/scala-2.11.6 export PATH=${SCALA_HOME}/bin:$PATH export SPARK_HOME=/opt/spark-hadoop/ export PYTHONPATH=/opt/spark-hadoop/python
2、运行效果:
python dataformat.py
python view.py
浏览器访问 http://0.0.0.0:8080/
主界面:
上传图片:
生成报告(注:此处可对数据进行修改调整):
进行预测:
附:版本库地址
学号:SA16225281
姓名:王陈航