NP2016

1. 项目简介

(1) 神经网络实现手写字符识别系统

  • 使用带标签的手写字符数据训练一个神经网络模型,并使用BP反向传播算法对参数进行校正,最终建立一个手写字符训练模型。用户可以在web端的canvas画布上手写数字,当点击Test的时候,就会将该手写字符数据传输到训练模型中,然后返回其预测值;当点击Train的时候,就可以根据人提供的真实数据进行训练。
  • 通过学习这个已经较完善的项目并将其跑起来,使得大家对神经网络的各项概念及实现有了初步的理解。虽然项目难度很友好,代码也都是现成的,但当时实现时还是遇到了不畅,卡壳一段时间才发现是代码里有几处小bug,并不是可以直接拿来用的。这里对之后有一个很深刻的教训:永远不能掉以轻心,语法上的小问题也可以是拦路虎。

(2)基于机器学习神经网络的一个医学辅助诊断系统

  • 通过对病人血常规化验单的图片的识别采集数据进行分析。利用通过大量真实数据训练出来的有一定可接受的准确率的模型对病人所提供的数据进行判断,通过对各项血常规数据的分析来实现对病人性别和年龄的预测。

  • 项目地址:托管在coding.net上的np2016项目


2. 个人工作

2.0 概论

1.pr(已合并):

2.一次Presentation: pr #181的code review、

2.1 数据预处理

  1. 读取数据前, sex列本来是’男’ 和’女’, 预处理为整数: 男->1, 女->0. 直接使用sublime的查找替换功能实现
  2. 使用pandas读取数据, 读取后的数据格式为dataframe:
    data = pd.read_csv('data.csv', names=class_names)
  3. 丢掉缺失数据的行 在numpy中, nan代表缺失数据, 在csv中显示为空值, 例如: 1,,2, 第二个值缺失

    1
    data = data.dropna(how='any')
  4. 将数据集分裂为数据和标签

    1
    2
    3
    selected_names = [x for x in class_names if (x != 'age' and x != 'id' and x != 'sex')]
    X_data = data[selected_names].as_matrix()
    y_data = data['age'].as_matrix()
    • selected_names选择了除age, id和sex之外的所有列, 之所以排除id是因为id和性别年龄显然不相关
    • as_matrix()把pandas读取的dataframe转换成numpy数组
  5. 使用sklearn自带的train_test_split()按照1:4的比例随机分割数据集为训练集和测试集

    1
    2
    X_train, X_test, y_train, y_test = \
    train_test_split(X_data, y_data, test_size=0.20, random_state=0)
  6. 数据标准化 使用sklearn自带的StandardScaler()将数据变为平均值=0, 方差=1的标准数据

    1
    2
    3
    ss = StandardScaler()
    X_train = ss.fit_transform(X_train)
    X_test = ss.transform(X_test)

2.2 训练随机森林预测器

用sklearn中sklearn.ensemble.RandomForestClassifier随机森林算法来进行预测。随机森林,指的是利用多棵树对样本进行训练并预测的一种分类器。对于每棵树,它们使用的训练集是从总的训练集中有放回采样出来的,这意味着,总的训练集中的有些样本可能多次出现在一棵树的训练集中,也可能从未出现在一棵树的训练集中。

  1. 使用随机森林预测年龄

    1
    2
    clf = RandomForestRegressor(max_features=None, n_estimators=20, max_depth=None)
    clf.fit(X_train, y_train)
    • n_estimators=20表示使用20个决策树预测
    • max_features=None表示不限制特征数
    • max_depth=None表示不限制树的深度. 因为数据量足够大(样本容量/特征数=100), 故过拟合风险不大

2.3 选择最优特征, 压缩训练集

  1. 特征选择

    1
    2
    3
    4
    5
    6
    7
    8
    importances = clf.feature_importances_
    indices = np.argsort(importances)[::-1]
    model = SelectFromModel(clf, threshold=0.10, prefit=True)
    X_train_new = model.transform(X_train)
    X_test_new = model.transform(X_test)
    # 在新数据上重新训练模型
    clf.fit(X_train_new, y_train)
    • 训练好的模型clf内置了feature_importances_参数, 代表每个特征和预测值(年龄)之间的线性相关程度.
    • np.argsort(importances)[::-1]按相关程度倒序排列了各个特征.
    • SelectFromModel()中, threshold=0.10代表过滤掉相关程度小于0.1的特征.
    • model.transform()生成处理过的数据, 去掉了过滤掉的特征.
  2. 评估模型

    1
    2
    3
    4
    pd = clf.predict(X_test)
    delta = [x1 - x2 for (x1, x2) in zip(y_test, pd)]
    correct_indices = [x for x in delta if abs(x) < 5]
    precision = float(len(correct_indices)) / len(pd)
    • delta代表预测值和测试集中的真实值的差
    • correct_indices代表差值的绝对值小于5的数据的位置
    • correct_indices的长度除以测试集的长度, 就得到了准确率precision
  3. 特征选择效果

  • pr #181做了特征选择工作,设定阈值,提取了和预测值相关程度较高的几个特征,以此重新预测,达到优化的目的。在课堂分享过程中,孟老师提出我们可以研究下相关度较高的特征到底是哪几个。针对此,做了更新优化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
特征选择预训练中...
特征权值分布为:
1. PCT 13 (0.103101)
2. MCHC 9 (0.070611)
3. NEU% 20 (0.066954)
4. HGB 5 (0.065227)
5. MCH 8 (0.059460)
6. HCT 6 (0.043636)
7. BAS# 4 (0.041166)
8. MCV 7 (0.041074)
9. PDW 14 (0.040524)
10. MONO% 18 (0.038807)
11. MPV 12 (0.038634)
12. LYM% 16 (0.037980)
13. NRBC# 26 (0.037481)
14. RBC 3 (0.033709)
15. EOS# 21 (0.027879)
16. RDW-CV 10 (0.026259)
17. EOS% 22 (0.025277)
18. MONO 17 (0.024211)
19. BAS% 23 (0.023571)
20. LYM# 15 (0.015956)
21. NEU# 19 (0.015689)
22. PLT 11 (0.010343)
23. WBC 2 (0.003948)
24. IG% 25 (0.003307)
25. IG# 24 (0.002154)
训练集和测试集的容量以及选择的特征数为: (7731, 5) (859, 5)
训练集准确率为: 0.655930668736
测试集准确率为: 0.275902211874
  • 这里阈值设为了0.05, 故只选取了前5个特征作为训练集数据, 在训练集上达到了65%的准确率, 预测集上达到了27%的准确率, 说明还是有过拟合.

2.4 可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def draw(labels, prediction):
"""
绘制折线图比较结果
:param labels: 测试集中的真实值, 1维numpy数组
:param prediction: 模型的预测值, 1维numpy数组
:return:
"""
# 添加数据
result = []
for i in range(labels.shape[0]):
result.append([labels[i], prediction[i]])
# 将数据按照年龄大小排序
result = sorted(result, key=lambda x: x[0])
labels = [row[0] for row in result]
prediction = [row[1] for row in result]
# 绘图
plt.plot(labels, label='labels')
plt.plot(prediction, label='predict')
plt.legend(loc='upper left')
plt.show()

训练集效果:

预测集效果:

  • 准确率为25%~30%

3. 运行环境

3.1 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 安装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框架、mongo
sudo pip install Flask
sudo apt-get install mongodb # 如果找不到可以先sudo apt-get update
sudo service mongodb started
sudo pip install pymongo
# 用sklearn进行预测需要的框架
sudo apt-get install cython python-scipy
pip install -U scikit-learn
pip install pandas

3.2 文件介绍

view.py

Web 端上传图片到服务器,存入mongodb并获取oid,稍作修整,希望能往REST架构设计,目前还不完善; 前端采用了vue.js, mvvm模式。写了两个版本,一个是index.html无插件,另一个使用了bootstrap-fileinput插件,有点问题;

imageFilter.py

对图像透视裁剪和OCR进行了简单的封装,以便于模块间的交互,规定适当的接口

ocr函数 - 模块主函数返回识别数据

用于对img进行ocr识别,他会先进行剪切,之后进一步做ocr识别,返回一个json对象 如果剪切失败,则返回None @num 规定剪切项目数

perspect函数做 - 初步的矫正图片

用于透视image,他会缓存一个透视后的opencv numpy矩阵,并返回该矩阵 透视失败,则会返回None,并打印不是报告 @param 透视参数

  • 关于param
    参数的形式为[p1, p2, p3 ,p4 ,p5]。 p1,p2,p3,p4,p5都是整型,其中p1必须是奇数。

p1是高斯模糊的参数,p2和p3是canny边缘检测的高低阈值,p4和p5是和筛选有关的乘数。

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

就现在来看,得到较好结果的前提主要有三个:

  • 化验单尽量平整
  • 图片中应该包含全部的三条黑线
  • 图片尽量不要包含化验单的边缘,如果有的话,请尽量避开有阴影的边缘。

filter函数 - 过滤掉不合格的或非报告图片

返回img经过透视过后的PIL格式的Image对象,如果缓存中有PerspectivImg则直接使用,没有先进行透视 过滤失败则返回None @param filter参数

autocut函数 - 将图片中性别、年龄、日期和各项目名称数据分别剪切出来

用于剪切ImageFilter中的img成员,剪切之后临时图片保存在out_path, 如果剪切失败,返回-1,成功返回0 @num 剪切项目数 @param 剪切参数

剪切出来的图片在BloodTestReportOCR/temp_pics/ 文件夹下

函数输出为data0.jpg,data1.jpg……等一系列图片,分别是白细胞计数,中性粒细胞记数等的数值的图片。

classifier.py

用于判定裁剪矫正后的报告和裁剪出检测项目的编号

imgproc.py

将识别的图像进行处理二值化等操作,提高识别率 包括对中文和数字的处理

digits

将该文件替换Tesseract-OCR\tessdata\configs中的digits


4. 项目展示

  • 项目启动
1
2
cd BloodTestReportOCR
python view.py # upload图像,在浏览器打开http://yourip:8080
  • 选择图片上传

  • 生成报告

  • 进行预测