AICon全球人工智能与机器学习技术大会周四开幕,点击查看完整日程>> 了解详情
写点什么

60 分钟搞定,基于 ResNet 和 Azure GPU 加速的肺癌 CT 图像识别

  • 2017 年 5 月 24 日
  • 本文字数:10167 字

    阅读完需:约 33 分钟

用深度学习技术分析医学影像和视频是一个新的研究方向。通过已训练好的卷积神经网络,能很快地搭建并训练自己的深度学习系统。 本文介绍了微软的一个比赛队伍参加 2017 年 Kaggle 肺癌 CT 图像检测比赛,成功地借用现成的 152 层 ResNet 网络,对接到分布式计算的神经网络上,在 60 分钟内完成训练。并获得了 2017 年 Kaggle 数据科学比赛里很好的名次。

该系统证明了在预先训练好的模型基础上(ImageNet ResNet-152) 进行修改,用自己的数据继续更新权重,快速完成适合自己独特场景 (肺癌检测)的可能性。同时,有具体实施细节和代码可供大家参考,来搭建自己的由CNTK,LightBGM,ResNet 和GPU 组成的深度学习系统。本文对卷积神经网络、提升树等技术的基本概念也进行了讲解,希望对刚接触AI 的朋友有所帮助。

我们先从DICOM 医学影像格式起,讲讲如何使用医疗影像数据,再介绍卷积神经网络(CNN)的基本概念和过程,最后讲讲前面提到的2017 年Kaggle 比赛里用CT 图像检测肺癌的过程,如何用Resnet-152、LightGBM 在Azure GPU 虚机上,60 分钟内完成肺癌检测模型训练和结果提交。

用Python 进行图像处理的基础

用于图像处理的库有很多,其中OpenCV(Open computer vision) 比较主流,有强大的社区支持,并支持C++,JAVA 和python。

安装时,既可以用 pip install opencv-python,也可以从 opencv.org 下载源码。

如果习惯用 Jupyter notebook 这种开源在线笔记的话,可以先打开 Jupyter,导入 cv2。要安装 numpy 和 matplotlib,以便在 Jupyter 里查看绘制的图形。

复制代码
import numpy as np;
import cv2;
import matplotlib.pyplot as plt

然后在 Jupyter 里打开图像:

复制代码
#将彩色图片以灰度加载
img=cv2.imread('/user/img/dsc_0848.jpg')
plt.imshow(img)
plt.show()

基本脸部识别

用开源的由 Rainer Lienhart 开发的 ada 正面脸部检测工具,可以小试一下脸部识别。Haar 脸部级联检测的更多信息请见这里,也可以参考用 OpenCV 进行图像处理的例子

医学影像数据格式

医学影像常常以 DICOM 标准进行存储和交换。该标准包括文件格式和通信协议。

  • 文件格式:所有患者医疗图像都用 DICOM 格式保存。该格式包括患者健康隐私信息 PHI(protected health information),比如姓名,性别,和其他与图像相关的数据,如采集该图像的设备和相关治疗信息。医学影像设备创建 DICOM 文件,医生用 DICOM 查看器——用来展示 DICOM 图像,并对图像内容进行读取和诊断的应用程序。
  • 通信协议:用来搜索图像档案库。医学影像应用程序通过 DICOM 协议连到医院网络,并交换信息,主要是 DICOM 图像和小部分患者和治疗信息。更高级的网络命令可以控制和遵循治疗方案,安排治疗时间,汇报状态,和在医生和影像设备之间协调工作。

更多关于 DICOM 标准的内容,可以参见此博客

分析DICOM 图像

pydicom 是个很好的 python 包,可用来分析 DICOM 图像。让我们看看如何在 Jupyter notebook 里渲染 DICOM 图片。

先安装 pydicom:pip install pydicom,在 Jupyter notebook 里导入 dicom 之类的包:

复制代码
import cv2
import numpy as np
import matplotlib.pyplot as plt
import dicom as pdicom
import os
import glob
%matplotlib inline

也可以用 pandas, scipy, skimage, mpl_toolkit 等进行数据处理和分析。

复制代码
#其他用来分析的包
import pandas as pd # 数据处理,CSV 文件的输入输出
import scipy.ndimage
from skimage import measure, morphology
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

网上有很多免费的 DICOM 数据集,比较适合自己动手试验:

- Kaggle 竞赛数据集: 很好用,比如肺癌检测竞赛和糖尿病视网膜病变。

- DICOM 库:用于教学和科研的免费在线视频和图像。

- Osirix 数据集:提供各种技术的人类影像数据集。

- Zubal Phantom 两个男性的 CT 和 MRI 免费数据集。

下载数据,并在 Jupyter 里打开:

复制代码
#下载 DICOM 图像
INPUT_FOLDER='user/img/Downloads/uncompressed/'
patients=os.listdir(INPUT_FOLDER)
#将 DICOM 图片加载到 list 里
lstFilesDCM=[]
def load_scan2(path):
for dirName,subdirList,fileList in os.walk(path):
for filename in fileList:
if ".dcm" in filename.lower():
lstFilesDCM.append(os.path.join(dirName,filename))
return lstFilesDCM
first_patient=load_scan2(INPUT_FOLDER)

第一步:在 Jupyter 里展示 DICOM 图像:

复制代码
#加载第一个 DICOM 文件,获得 ref 文件。
RefDs=pdicom.read_file(lstFilesDCM[0])
#根据行、列和切片(沿 z 轴)的数量,对行数、列数和切片数赋值
ConstPixelDims=(int(RefDs.Rows),int(RefDs.Columns),len(lstFilesDCM))
#对沿 X,Y,Z 轴的间隔赋值(毫米)
ConstPixelSpacing=(float(RefDs.PixelSpacing[0],float(refDs.PixelSpacing[1],float(RefDs.SliceThickness))

再创建一个 3D Numpy 数组,并读入各个 DICOM 图像文件中的数据(灰度或 RGB 值)再用该 Numpy 数组实现图像分析、学习等步骤,具体步骤可参见下文的肺癌检测例子,或此处

第二步:DICOM 格式的细节

CT 扫描的指标 单位是 HU,记录放射密度。CT 需要经仔细地校准,来准确地记录 HU 值。更进一步的信息可参考这里

每个像素都被分配一个数值,即相应体素(Voxel)里所有衰减值的平均值。把它和水的衰减值相比,转化成一个任意单位的值,称之为HU 值,因为它是以Sir Godfrey Hounsfield 命名的。

水的HU 值为0,CT 值的范围是2000 HU,而有些现代CT 机能达到4000 HU。每个值用一个灰度表示,1000 是白色,-1000 是黑色。

下面,我们先介绍一下卷积神经网络的基础,再讲讲如何用它来进行图像分析。

卷积神经网络基础

神经网络

先借用 CNTK 的一个例子,来看看如何使用神经网络来进行分类。如果想根据一个人的年龄和年收入,对他的政治倾向进行分类(保守派,居中和自由派),怎么做呢?答案是通过已知值,建立并训练一个预测模型。该模型是某种函数,由两个输入产生一个结果,该结果能够解释他属于哪个政治派别。

训练和测试数据。输入值 X1 和 X2 分别代表年龄和收入,三个颜色的点代表政治倾向类型。实心点代表训练数据,空心点代表预测结果。本图源自 MSDN 的 Machine Learning - Exploring the Microsoft CNTK Machine Learning Tool 一文。

基本的神经网络,本图源自 MSDN 的 Machine Learning - Exploring the Microsoft CNTK Machine Learning Tool 一文。

这就可以用一个简单的神经网络来解决:两个输入节点 (input nodes,分别代表年龄和收入) 和三个输出节点 (output nodes 分别代表三个政治倾向的概率)。输入(8.0, 3.0) 时, 输出 (0.3090, 0.0055, 0.6854)。该网络还有 5 个隐藏节点 (hidden nodes)。每个节点用从 0 开始的编号来代表,比如 input[0] 是最靠左上角的节点,output 2 是最右下角的。

中间的连线叫做权重(weights),比如,从 input[0] 到 hidden[0] 的权重为 2.41。

计算隐藏节点的输出值时,先将每个输入的权重乘以所连的节点的值,将所有乘积叠加,并加上偏置量,再输入隐含节点的激活函数,即可得到输出。

常见的隐藏层激活函数包括 tanh(双曲正切函数)、Sigmoid 和 ReLU。

常见的激活函数:Sigmoid,双曲正切和 ReLU,图片源于 A Practical Introduction to Deep Learning with Caffe and Python 一文。

以此类推: output[1]=0.0055, output[2]=0.6854。output[2] 代表的概率最高,0.6854,因此对(8.0,3.0)的分类是“绿色”,即“自由派”。这些工作是由最后一层:全互联层(Fully Connected Layer) 来完成。

用于图像识别的神经网络

图像由像素组成,彩色图像包括 RGB(红绿蓝)分量,每个分量的轻重由灰度值表示。 因此,神经网络的输入是 N 维数组,长宽深,分别由 RGB 三个颜色分量组成。

图片源于 http://xrds.acm.org/blog/2016/06/convolutional-neural-networks-cnns-illustrated-explanation/

我们希望通过神经网络后,得到一组数字,来描述每种结果的可能性。对肺癌图像而言,我们希望得到“阴性”和“阳性”这两种情况的各自概率。

图像通过卷积神经网络的卷积层、激活层、池化层、批量归一化层和全互联层(Fully Connected Layer)将产生上述结果。

感谢 *A beginner’s guide to understanding convolutional Neural Networks*一文

实际上,不需要通过卷积,也可以实现图像识别——只需要用样本图像的二维数组+ 标签,训练神经网络,也可以产生一样的效果。数据量越大,效果越好。

为什么要在“神经网络”前加“卷积”?

答案很简单。如果要识别一个在图片里位置居中的手写的“8”,用神经网络+ 一张NVIDIA GTX 1080 就可以搞定,无需卷积。然而,同样的“8”,如果位置不居中,在画面的左上角、右下角或缩小50% 等等,神经网络都会认为是完全不同的“8”。如果训练数据没有类似的位置和大小,就无法准确识别。

因此,我们在神经网络之前,加了一个重要环节:特征提取。这就是卷积的重要功能之一:它将一个小过滤器(“神经元”或“卷积核”),比如图案“8”,一步步地平移扫描整个图像。无论“8”的位置是在左上角,还是右上角,它都能找出来。卷积的结果就是“特征”, 一个数字组成的数组。

然后,再将特征数组,送到神经网络里,让神经网络对这个数组进行判断,而不是对图像本身判断,结果就好得多。

卷积神经网络

卷积是两个函数和的计算:将翻转平移,和相乘后,做积分。得到。

两个方形脉冲和的卷积。先对 反转,接着平移"t",成为。重叠部分的面积(黑色实线),即为"t"处的卷积。横坐标为 及的自变量"t"。

方形脉冲和指数衰退脉冲的卷积。重叠部分面积(黑色实线)即为处的卷积。

对于离散的矩阵A 和B,卷积也是一样的:先将矩阵B 翻转,再平移, 每个元素和矩阵A 相应的位置的元素相乘后的总和,即得出该位置的卷积。

5x5 的绿色矩阵 A 和 3x3 的矩阵 B 卷积,得出粉红色矩阵 C。矩阵 B 每滑动一步,和矩阵 A 的 [点积][14] 就是该位置的卷积值。

如前所述,如果矩阵 A 是输入的图像。矩阵 B 是过滤器。通过卷积运算,能将各种图案特征分析出来,比如弧线、边缘、色块等等,得到一个较小的矩阵,叫做激活地图(activation map)。 举一个过滤器为例:

比如,要分析出一段长弧线,(本图来自 A beginner’s guide to understanding convolutional Neural Networks一文)

用第一个矩阵是原图像,第二个矩阵为过滤器(或卷积核),两者卷积结果——激活地图上在弧线位置的值较大:6600,而在其他没有这样大弧线的位置,卷积值较小。(本图来自A beginner’s guide to understanding convolutional Neural Networks一文)

实际用于图像分析的卷积是三维的,请见下图。比如,彩色图像包括RGB 三种色彩,因此,输入除了长、宽两个维度之外,还有厚度(“3”)。因此要检测某种图案特征,卷积核或过滤器实际上是三维的,如图中的粉红色方块。

这仅仅是一种图像特征的过滤器,而更多的特征还需要更多的过滤器。最终的激活地图是将多个过滤器的卷积结果沿厚度方向“叠”起来,因此用了多少个过滤器,其结果(激活地图)的“厚度”就是多少。

由RGB 三个颜色通道组成的三层图像(第一个浅蓝方块体),与粉红色的3 维过滤器(厚度同样为3)卷积后,得到一个面。用96 个不同的过滤器卷积,得到96 层,即厚度为96。这96 个过滤器分别检测不同特征。

那么这些过滤器里的值怎么确定,是按经验值或前人的试验来设定的吗? 不是,是机器自己“琢磨”出来的,换句话说,是神经网络训练出来的,过程如下,图片源于 Machine Learning is Fun!

第一步:将整个图像分为 N 小块。每小块分为 RGB 三层,是个厚度为 3 的立方体。

第二步:每小块都接到同样的神经网络(过滤器,同样是 3 层)。

第三步:每小块的输出都按输入的位置,依次排列。并通过激活函数,比如用 ReLU 将负数变为零。对 N 种过滤器都重复此过程,形成厚度为 N 的结果集。

第四步:池化,将长宽变小,以便减少参数和计算量。比如每四个输出中,仅保留最大值。这些结果即是“特征”,同样,有 N 种过滤器,会形成厚度为 N 的结果集。

上图中第二步得到激活地图。通过激活层,比如用 ReLU 作为激活函数,将激活地图上所有负数将变为零,正数将不变。

再通过池化层,将特征地图的空间尺寸逐渐缩小,以便减少网络里的参数和计算量,并控制过拟合。池化层对输入矩阵沿厚度方向的每个切片进行操作,求最大值或平均值来调整其空间大小。

最常见一种池化层是用 2x2 的 Max-Pooling 过滤器。

池化层在空间上缩减样本,在厚度方向保持不变。 左图:通过尺寸为 2、步长为 2 的过滤器,将尺寸 224x224x64 缩减为 112x112x64,而厚度方向尺寸不变。右图:最常用的缩减运算是“最大值”,即求出 2x2 格子(同样颜色)里的最大值,然后右移 2 格(步长为 2)。

第五步:将第四步输出的“特征”输入另一个神经网络,进行分类。该层叫做“全互联层”。

实际的神经网络会由多个卷积层和池化层组成。每个卷积层实际会用 N 个不同过滤器,得到 N 个结果,叠起来形成厚度 N。比如第一层卷积会用 96 个检测不同特征的过滤器,因此厚度为 96. 此例将 224x224x3(RGB 三色)的图像经过两次卷积 + 池化,再经过 3 次卷积,一次池化,和两个全互联层(即 dense layer),得到最终的 1000 种分类。

一个卷积神经网络里,可以由多个卷积层和池化层组成。每层如何决定权重,完全由训练产生,没有人工干预。不过,有人逐层分析已经训练好的神经网络,发现前面的卷积层寻找的是简单的特征,比如边缘或曲线。而后面的卷积层寻找的是人脸、皱纹等更高级的特征。

最后,将后面得到的高级特征,输入另一个全互联神经网络层和SoftMax 函数,即可获得一组输出值:比如每种分类标签的概率。

后面的肺癌CT 识别的案例里,正是借用了ImageNet 用来进行图像分类的ResNet-157 模型,将全互联层的输入,转接到LightGBM。利用已训练好的ResNet-157 计算CT 图像的特征值, 并用这些特征值和标签训练LightGBM 的神经网络,并实现对肺癌CT 的识别。

训练神经网络

前面提到,所有过滤器的值都是通过训练获得,哪些是肺癌的特征?是机器自己“琢磨”出来的,那么,到底是怎么算出来的呢? 答案是,通过用一组已知的图片和各自对应的标签(Label) 来训练神经网络。 训练过程将逐渐调整各个过滤器的每个值(或权重),也称之为反向传播(Back propogation)。反向传播包括四个计算步骤:前向传导(forward pass)、损失函数(loss function)、后向传导(backward pass) 和权重更新(weight update)。

先将所有过滤器和权重设为任意数。前向传导是将样本图像输入神经网络,按上面提到的办法计算得到输出。因为在训练阶段知道正确的样本图像分类结果,所以可以算出计算结果和正确结果之间的差距, 记为“L”,比如用均方差MSE 作为L,这就是一种损失函数。

注意,每次更新权重时,{同一个小批里n 个训练样本的的平均值}。有时候用完样本数据后,得分可能还是不收敛,就要将样本数据重新排序(shuffle),取另一套测试数据,继续训练。

防止过拟合

如何避免过拟合是机器学习里非常重要的考虑因素。如果权重和偏置量(Bias) 过于“贴近”训练数据,很可能造成模型对新数据不够适应,实际运用时产生很糟糕的效果。更确切地说,如果训练所用的数据少,又追求尽可能拟合绝大多数数据,那么很可能拟合了训练数据里的“噪音”或异常值,弄得模型敏感而复杂,因此,最糟糕的训练是仅用一套数据训练所有节点。后面提到的Early_stop_ping_round, L1 和 L2 ,交叉验证,及剔除(Dropout)都是避免过拟合的措施。

剔除(Dropout)

剔除能够在训练阶段快速检验是否过拟合。如果模型没有问题,那么将神经网络里的部分节点去掉,也应该产生正确的结果。

神经网络的剔除 左图:带有两个隐藏层的神经网络; 右图:经过剔除的一个子网络。叉代表被剔除的隐藏和输入节点;

图片源于 Dropout: A Simple Way to Prevent Neural Network from Overfitting。

采用剔除时,每个子网络的每个输入节点和隐藏节点是否出现,由概率 p 随机决定连接。被剔除的节点权重也剔除掉,如上图的右图 (b) 所示。一般来说,将保留大部分输入节点和部分隐藏节点(比如50%)

举一篇关于剔除的经典论文 Dropout: A Simple Way to Prevent Neural Network from Overfitting 里所采用的的剔除为例:未使用剔除时,训练过程的每个小批次(mini-batch) 里的 n 个图像样本所训练的都是同样的一个网络,如上图 (a)。而采用剔除时,每个训练样本都对应一个不同的经随机剔除的子网络,如上图(b)。因此,每个小批次中的 n 个图像样本,将分别对应 n 个子网络,而每次 epoch 循环更新权重 w 时,用的是 n 个的平均值。

验证时,不再剔除,而是用完整的网络,每个权重等于训练结果中的权重与相应概率的乘积,即(训练权重 w),如下图。

从训练到最终的网络 左图:在训练阶段,每个输入和隐藏节点存在的概率为 p,和下层网络的连接权重为 w; 右图:在验证阶段,每个节点都保留,权重是 p 和 w 的乘积,模型的输出是训练阶段所有输出的期望值;

图片源于 Dropout: A Simple Way to Prevent Neural Network from Overfitting。

交叉验证

如何保证模型在新的数据下,仍有较好表现?一般会将所有样本数据的一部分预留起来,用剩下的训练模型。等训练结束后,再用预留的数据验证,看真实效果如何。这是避免过拟合的重要措施。

但,这样做,使得用于训练模型的数据少了,且选择哪部分数据用于训练,哪部分用于验证,也可能影响效果。交叉验证是个解决方案。比如以 K 折交叉(K-fold cross validation) 验证为例:将样本数据分成 K 份,先选一份(A)作为验证数据,预留起来。用剩下的 K-1 份训练网络,再用预留的那份(A)做验证。

然后,再另选一份作为验证数据(B),同样重复上述步骤,用剩下的 K-1 份训练,并用 B 做验证。以此类推,K 次之后,所有的数据都做过验证数据。最终的验证得分是 K 次验证得分的平均。

scikit-learn 的 train_test_split 函数可以很容易地按某个百分比,将数据拆分成训练和验证的两部分。用 cross_val_score(clf,x_data,y_label,cv=num_K_fold) 可以很容易地用设置好的分类器 clf,进行折数为 num_K_fold 的 K 折交叉验证。

肺癌 CT 影像识别

微软的 Miguel Fierro, Ye Xing, Tao Wu 等人在 2017 年 1 月 Kaggle 上的“数据科学肺癌检测竞赛”里,在60 分钟内,利用已训练好的卷积神经网络ResNet-157 提取CT 图像的特征,并训练提升树(boosted tree),以识别肺癌CT 影像是癌症阴性或阳性。他们获得了不错的成绩——1 月19 日之前排名位前10%,2 月7 日之前,居前20%。

所用到的工具包括:

1. 特征提取:已训练好的卷积神经网络——一个用 CNTK 开发的 152 层 ResNet 模型,用 ImageNet 的图像数据集进行的训练;

2. 图像分类: LightGBM 灰度提升框架;

3. 带GPU 加速的Azure 虚机

具体代码请见Jupyter notebook 里的笔记。用CNTK 和ResNet-157 计算特征用了53 分钟(如果用更简单的18 层ResNet 模型,需29 分钟),训练LightGBM 用了6 分钟。代码请见 Kaggle

训练速度对获奖来说非常重要。CNTK 和 LightGBM,加上 Azure 的高性能 GPU 虚机,为他们按需提供高性能计算环境,效果很好。下面,我们具体看看他们怎么做的。

用 ResNet 模型、CNTK 和 LightGBM 实施图像识别

深度学习的一种较新的办法是用已训练好的卷积神经网络作为基础,利用已经训练好的模型、权重来加快速度。这也是该团队所采用的方法。他们用 ImageNet 已训练好的卷积神经网络 ResNet-152 作为基础,来提取图像特征,然后将特征输入 LightGBM 灰度提升树框架(包括了 GBDT,GBRT,GBM,MART 等),来进行训练和分类。

该模型的前面几层会提取基本的特征,比如色块、弧线等。后面的层会识别更高层次的特征,比如结节等,更后面的层再进一步识别更复杂的特征,比如和恶性结节等肺癌特征更加贴近的特征。

让我们先看看 ImageNet 如何用卷积神经网络进行图像分类,见上图。输入是 224x224 的彩色图像,每个图像包括 RGB 三个色彩分量,记为 224x224x3。每个卷积层进行卷积计算,用多个厚度为 3 的过滤器,提取各种图像特征,因此,每层的输出的厚度随着过滤器的数量增加而变厚。

最后,由池化 +SoftMax 函数组成的分类器将输出一组由 1000 种概率组成的向量(最后的一组黑色小圈),对应于 ImageNet 的 1000 种不同图像分类。该概率向量中,哪个分类的概率最高,就是识别结果。比如上图中用猫照片输入 ResNet,经过每层卷积计算后,图片大小变小,厚度增加,直到最后的判断结果,即图中第二个小黑点的概率值最高,即识别结果:“虎斑猫”。

用已训练好的 ImageNet 的 ResNet-152 模型输出特征,输入到 LightGBM 中进行分类。

为了借用 ImageNet 的特征提取,来实现肺癌图像分类,先将由池化 +SoftMax 函数组成的分类器去掉,而将图中的"penultimate layer"的节点的输出作为 LightGBM 分类器的输入。 同时,每个患者的 CT 套图是黑白的,所以要先像 ImageNet 的 RBG 样本那样,裁切到 224x224 大小,三张一组。分成 K 批输入前面提到的已训练的神经网络,计算到“pentimate layer”为止。该过程用 CNTK 执行。将输出结果输入灰度提升树,用 LightGBM 分类。

LightGBM 是微软 DMTK 框架的一部分,将灰度提升树用于分布式集群上,以达到更快的速度,而且随着节点数增加,可以成比例地提高计算性能。据好事者测,比XGBoost 快10 倍,内存使用稍微少些。作为微软AI 的两大利器, DMTK(Distributed Machine Learning Toolkit)主要优势是分布式,而非深度学习。 CNTK 的优势在于深度学习,所以这两种常常一起用。

ResNet

为什么要用 ImageNet 的 ResNet-152 模型? ResNet 是微软亚洲研究院 Kaiming He 等人提出的。传统神经网络层数较多时,优化比较困难,因此拟合不够。而且如果仅仅增加层数,得到的训练误差和实验误差可能更高。而 ResNet 收敛更快,更容易训练,层数也可以更多,误差更低。比如,在 2015 年 ResNet 一战成名的 ILSVRC 比赛中,它囊括 ImageNet 和 COCO 的图像分类、物体检测等五项比赛冠军。2014 年冠军用的是 GoogleNet 的 22 层网络,而 2015 年的冠军 ResNet 有 152 层,将图像分类的误差从6.7% 缩减到3.57% ,而物体检测mAP 指标从2014 年16 层VGG 的66%,提升到101 层ResNet 的86%。下图是2016 年10 月25 日为止,由Eugenio Culurciello 发表的,对AlexNet, VGG 和ResNet 的比较。

感谢[Eugenio Culurciello][33]

步骤

具体安装和运行方法,请见 Cortana Intelligence 的笔记,大概分为以下步骤:

1. 安装以下内容: CUDA 8.0 RC1 , cuDNN 5.1 , Anaconda 4.2.0 , OpenCV , Scikit-learn 0.18 , CNTK 2.0beta9 (并打开1-bit SQG , 在多服务器多GPU 上更快), LightGBM 已训练的ResNet 的CNTK 模型;

2. 下载 DICOM 数据,最大文件 67GB,解压后 141GB;

3. 用 Python 加载库,设置路径和参数;

4. 用 get_3d_data 将某患者的所有 CT 切片读入一个 numpy 数组。因为卷积神经网络是用 ImageNet 的彩色图像进行训练的,所以要把样本按 ImageNet 的彩色图像进行规范——用 get_data_id 将该患者所有 CT 切片的对比度归一化,均衡直方图,再将每三张灰度图编为一组,来替代 ImageNet 彩图的 RGB 三分量,将每张灰度图的大小修改为 224x224。

5. 加载 ImageNet 模型,选取分类之前的一层节点 (“Penultimate”层,在 CNTK 中叫做“z.x”) 的输出"net"作为特征值(feature),。

6. 计算每个患者的 CT 切片的特征值,并存成二进制文件.npy。具体来说,用已经训练好的 ResNet-152 模型,直接进行前向传导(Forward Pass),而无需计算导数或更新 ResNet-152 模型的权重。因此,将第 4 步中每个患者的 CT 切片图像,每三张一组(“batch"或前文提到的"mini-batch”)计算特征(net.eval(batch)),并将结果存入该患者的.npy 文件。

7. 用特征 + 标签来训练 LightGBM,对 LightGBM 的回归器(Regressor)进行拟合。具体来说,每个患者都对应一个标签,“阳性”或“阴性”。先将该患者的每组(batch)三张切片的特征值的平均,作为该组的特征,再将该患者所有切片组(batch)的特征结果扁平化,形成一维 numpy 向量 X,并对应相应的标签 Y(阴性或阳性)。

8. 按交叉验证的办法,将 X 和 Y 分为训练组和验证组。再用 LGBMRegressor 创建分类器,用 fit 函数进行拟合。优化指标可以有多种选择,本例里,多次尝试下来用 L2 效果更好。将提前退出次数 Early_stop_ping_round 设为 300 次,模型训练到最近 300 次的 L2 指标不再改善时,即结束训练。

9. 用训练好的模型对图像样本进行预测。

第 8 步:训练 LightGBM 的回归器时,提前退出次数 early_stop_ping_round 设为 300,因此当 L2 指标在最近的 300 次迭代后不再改善时,即结束训练。[39]

这个例子比较典型地展示了用卷积神经网络实现医学图像自动检测的方法。 用 Azure 的 GPU 虚机,和 CNTK 技术,快速地搭建并完成训练,其速度之快让人刮目相看。既反映了微软在人工智能和云计算的技术实力,又显示了 ResNet 的优势。对于刚开始接触深度学习和卷积神经网络的朋友来说,是个难得的可以自己动手的机会。


感谢杜小芳对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 5 月 24 日 17:054899

评论

发布
暂无评论
发现更多内容

JVM - GC 问题分析常用两大指标

insight

3月日更

宣传

Ashley.

微信聊天记录里的文件又失效了?试试这个文件同步开源项目吧

HelloGitHub

Go 开源 文件传输

How to Connect 2 Cisco Switches Together

心在飞

Ubuntu 日常使用问题及解决

依旧廖凯

28天挑战 3月日更

与前端训练营的日子 -- Week20

SamGo

学习

在深圳,看见大鲸跃起的浪潮

浪潮云

工业互联网

基于NA公链(Nirvana)的应用和NAC公链社区共识探究公链的发展未来

区块链第一资讯

javax.imageio.IIOException: Unsupported Image Type

wjchenge

技术实践丨Prometheus+Grafana搭建HBase监控仪表盘

华为云开发者社区

开源 Grafana Prometheus HBase 开源数据库

Gradle无法访问Nexus私服仓库-offline

wjchenge

《Redis 核心技术与实战》学习笔记 06

escray

redis 学习 28天写作 3月日更 Redis 核心技术与实战

ES6中的新特性:Iterables和iterators

程序那些事

nodejs ES6 程序那些事

Flutter开发:给image设置圆角的方法

三掌柜

flutter 3月日更

MongoDB中的正则表达式

Kylin

mongodb 3月日更 21天挑战

翻译:《实用的Python编程》07_03_Returning_functions

codists

Python

前端开发:自定义搜索框(含联想搜索)

三掌柜

vue.js 前端 3月日更

vivo 应用商店推荐系统探索与实践

vivo互联网技术

架构 推荐系统 服务器

写代码的这群人不写文档,为什么 (上篇)

微言码道

程序员 架构

字符编码,原来是SQL不走索引的元凶之一!

Java小咖秀

MySQL 程序员 开发 bug 细节

产品经理面试常见问题总结2

lenka

3月日更

实操 | 剖析 Java16 新语法特性

九叔

Java 架构

基于数组或链表实现Map

Silently9527

数据结构和算法

LiteOS:剖析时间管理模块源代码

华为云开发者社区

时间管理 时间 LiteOS huawei 任务

彩色图像的二值化,取经之旅第 6 天

梦想橡皮擦

28天写作 3月日更

Swagger增强神器:Knife4j!用它轻松实现接口搜索、Word下载、接口过滤...

王磊

Java swagger Knife4j

分布式任务 + 消息队列框架 go-queue

万俊峰Kevin

Go 微服务 分布式任务 消息队列

寻找被遗忘的勇气(二十一)

Changing Lin

3月日更

翻译:《实用的Python编程》07_02_Anonymous_function

codists

Python

跟随报文,开启一段奇妙之旅

华为云开发者社区

报文 Windows主机 路由器 Linux主机 路由表

【LeetCode】位1的个数Java题解

HQ数字卡

算法 LeetCode 3月日更

数据cool谈(第2期)寻找下一代企业级数据库

数据cool谈(第2期)寻找下一代企业级数据库

60分钟搞定,基于ResNet和Azure GPU加速的肺癌CT图像识别-InfoQ