做者|Marcelo Rovai
编译|VK
来源|Towards Data Sciencecss
本研究是为X光图像中COVID-19的自动检测而开发的,彻底是为了教育目的。因为COVID-19没有通过专业或学术评估,最终的应用并不打算成为一个准确的用于诊断人类的COVID-19的诊断系统,。html
Covid-19是由一种病毒(SARS-CoV-2冠状病毒)引发的大流行性疾病,已经感染了数百万人,在几个月内形成数十万人死亡。前端
据世界卫生组织(WHO)称,大多数COVID-19患者(约80%)可能无症状,约20%的患者可能由于呼吸困难而须要住院治疗。在这些病例中,大约5%可能须要支持来治疗呼吸衰竭(通气支持),这种状况可能会使重症监护设施崩溃。抗击这一流行病的关键是快速检测病毒携带者的方法。python
冠状病毒是引发呼吸道感染的病毒家族。这种新的冠状病毒病原体是在中国登记病例后于1919年末发现。 它会致使一种名为冠状病毒(COVID-19)的疾病。git
1937年首次分离出人冠状病毒。然而,直到1965年,这种病毒才被描述为冠状病毒,由于它在显微镜下的轮廓看起来像一个树冠。在下面的视频中,你能够看到SARS-CoV-2病毒的原子级三维模型:github
近年来,基于计算机断层扫描(CT)的机器学习在COVID-19诊断中的应用取得了一些有但愿的成果。尽管这些方法取得了成功,但事实仍然是,COVID-19传播在各类规模的社区。web
X光机更便宜、更简单、操做更快,所以比CT更适合在更贫困或更偏远地区工做的医疗专业人员。算法
对抗Covid-19的一个重大挑战是检测病毒在人体内的存在。所以,本项目的目标是使用扫描的胸部X光图像自动检测肺炎患者(甚至无症状或非病人)中Covid-19的病毒。这些图像通过预处理,用于卷积神经网络(CNN)模型的训练。flask
CNN类型的网络一般须要一个普遍的数据集才能正常工做。可是,在这个项目中,应用了一种称为“迁移学习”的技术,在数据集很小的状况下很是有用(例如Covid-19中患者的图像)。后端
目的是开发两种分类模型:
Covid-19的检测与胸片检测正常的比较
Covid-19的检测与肺炎患者的检测的比较
按冠状病毒相关论文定义的,全部类型的肺炎(COVID-19病毒引发的除外)仅被认为是“肺炎”,并用Pneumo标签(肺炎)分类。
咱们使用TensorFlow 2.0的模型、工具、库和资源,这是一个开源平台,用于机器学习,或者更准确地说,用于深度学习。最后在Flask中开发了一个web应用程序(web app),用于在接近现实的状况下进行测试。
下图为咱们提供了最终应用程序如何工做的基本概念:
X光扫描胸部图像(User_A.png),应用程序将图像存储在web应用程序的计算机上,决定图像是否属于受病毒污染的人(模型预测:[阳性]或[阴性])。在这两种状况下,应用程序都会通知预测的准确性(模型准确度:X%)。
为了不二者都出错,将向用户显示原始文件的名称及其图像。图像的新副本存储在本地,其名称添加一个预测标签,而且加上准确度。
这项工做分为四个部分:
环境设置、数据清洗和准备
模型1训练(Covid/正常)
模型2训练(Covid/肺炎)
Web应用的开发与测试
该项目的灵感来源于UFRRJ(里约热内卢联邦大学)开发的X光COVID-19项目。UFRRJ的XRayCovid-19是一个正在开发的项目,在诊断过程当中使用人工智能辅助健康系统处理COVID-19。该工具的特色是易用、响应时间快和结果的有效性高,我但愿将这些特色扩展到本教程第4部分开发的Web应用程序中。下面是诊断结果之一的打印屏幕(使用了Covid-19数据集1图像之一):
乔杜里等人在论文中阐述了该大学开展这项工做的科学依据,论文地址:https://arxiv.org/abs/2003.13145
另外一项工做是Chester,论文:https://arxiv.org/pdf/1901.11210.pdf,由蒙特利尔大学的研究人员开发。 Chester是一个免费且简单的原型,医疗专业人员可使用它们来了解深度学习工具的实际状况,以帮助诊断胸部X光。 该系统被设计为第二意见,用户可在其中处理图像以确认或协助诊断。
当前版本的 Chester(2.0)使用DenseNet-121型卷积网络训练了超过10.6万张图像。该网络应用程序未检测到Covid-19,这是研究人员对应用程序将来版本的目标之一。 下面是诊断结果之一的截图(使用了Covid-19数据集的图像)
在下面的连接中,你能够访问Chester,甚至下载应用程序供脱机使用:https://mlmed.org/tools/xray/。
这项工做最初是根据Adrian Rosebrock博士发表的优秀教程开发的,我强烈建议你深刻阅读。此外,我要感谢Nell Trevor,他根据罗斯布鲁克博士的工做,进一步提出了如何测试结果模型的想法。
训练模型以从图像中检测任何类型的信息的第一个挑战是要使用的数据量。 原则上,可公开获取的图像数量越多越好,可是请记住,这种流行病只有几个月的历史,因此对于Covid-19检测项目来讲,状况并不是如此)
可是,Hall等人的研究,论文:https://arxiv.org/pdf/2004.02060.pdf,证实使用迁移学习技术仅用几百幅图像就能够得到使人鼓舞的结果。
如引言所述,训练两个模型;所以,须要3组数据:
为此,将下载两个数据集:
Joseph Paul Cohen和Paul Morrison和Lan Dao COVID-19图像数据收集,arXiv: 2003.11597, 2020
这是一个公开的COVID-19阳性和疑似患者和其余病毒性和细菌性肺炎(MERS、SARS和ARDS)的X光和ct图像数据集。
数据是从公共来源收集的,也能够从医院和医生处间接收集(项目由蒙特利尔大学伦理委员会批准,CERSES-20-058-D)。如下GitHub存储库中提供了全部图像和数据:https://github.com/ieee8023/covid-chestxray-dataset。
论文:Kermany, Daniel; Zhang, Kang; Goldbaum, Michael (2018), “Labeled Optical Coherence Tomography (OCT) and Chest X-Ray Images for Classification”
经过深度学习过程,将一组经验证的图像(CT和胸片)归类为正常和某些肺炎类型。图像分为训练集和独立的患者测试集。数据可在网站上得到:https://data.mendeley.com/datasets/rscbjbr9sj/2
从数据集中,能够找到三种类型的图像,PA,AP和Lateral(L)。L的很明显,但X光的AP和PA视图有什么区别?简单地说,在拍X光片的过程当中,当X光片从身体的后部传到前部时,称为PA(后-前)视图。在AP视图中,方向相反。
一般,X光片是在AP视图中拍摄的。可是一个重要的例外就是胸部X光片,在这种状况下,最好在查看PA而不是AP。但若是病人病得很重,不能保持姿式,能够拍AP型胸片。
因为绝大多数胸部X光片都是PA型视图,因此这是用于训练模型的视图选择类型。
理想的作法是从一个新的Python环境开始。为此,使用Terminal定义一个工做目录(例如:X-Ray_Covid_development),而后在那里用Python建立一个环境(例如:TF_2_Py_3_7):
mkdir X-Ray_Covid_development cd X-Ray_Covid_development conda create — name TF_2_Py_3_7 python=3.7 -y conda activate TF_2_Py_3_7
进入环境后,安装TensorFlow 2.0:
pip install — upgrade pip pip install tensorflow
从这里开始,安装训练模型所需的其余库。例如:
conda install -c anaconda numpy conda install -c anaconda pandas conda install -c anaconda scikit-learn conda install -c conda-forge matplotlib conda install -c anaconda pillow conda install -c conda-forge opencv conda install -c conda-forge imutils
建立必要的子目录:
notebooks 10_dataset — |_ covid [here goes the dataset for training model 1] |_ normal [here goes the dataset for training model 1] 20_dataset — |_ covid [here goes the dataset for training model 2] |_ pneumo [here goes the dataset for training model 2] input - |_ 10_Covid_Imagens _ | |_ [metadata.csv goes here] | |_ images [Covid-19 images go here] |_ 20_Chest_Xray - |_ test _ |_ NORMAL [images go here] |_ PNEUMONIA [images go here] |_ train _ |_ NORMAL [images go here] |_ PNEUMONIA [images go here] model dataset_validation _ |_ covid_validation [images go here] |_ non_covidcovid_validation [images go here] |_ normal_validation [images go here]
下载数据集1(Covid-19),并将metadata.csv文件保存在/input/10_Covid_Images/和/input/10_Covid_Images/Images/下。
下载数据集2(肺炎和正常),并将图像保存在/input/20_Chest_Xray/下(保持原始测试和训练结构)。
从GitHub下载Notebook:https://github.com/Mjrovai/covid19Xray/blob/master/10_X-Ray_Covid_development/notebooks/10_Xray_Normal_Covid19_Model_1_Training_Tests.ipynb。将其存储在 subdirectory /notebooks中。
进入Notebook后,导入库并运行支持函数。
从输入数据集(/input/10_Covid_Images/)建立用于训练模型1的数据集,该数据集将用于Covid和normal(正常)标签订义的图像分类。
input_dataset_path = ‘../input/10_Covid_images’
metadata.csv文件将提供有关/images/文件中的图像的信息。
csvPath = os.path.sep.join([input_dataset_path, “metadata.csv”]) df = pd.read_csv(csvPath) df.shape
metadat.csv文件有354行28列,这意味着在subdirectory /notebooks中有354个X光图像。让咱们分析它的一些列,了解这些图像的更多细节。
经过df.modality,共有310张X光图像和44张CT图像。CT图像被丢弃。
COVID-19 235 Streptococcus 17 SARS 16 Pneumocystis 15 COVID-19, ARDS 12 E.Coli 4 ARDS 4 No Finding 2 Chlamydophila 2 Legionella 2 Klebsiella 1
从可视化角度看,COVID-19的235张确认图像,咱们有:
PA 142 AP 39 AP Supine 33 L 20 AP semi erect 1
如引言中所述,只有142张PA型图像(后-前)用于模型训练,由于它们是胸片中最多见的图像(最终数据框:xray_cv)。
“xray_cv.patiendid”列显示,这142张照片属于96个病人,这意味着在某些状况下,同一个病人拍摄了多张X光片。因为全部图像都用于训练(咱们对图像的内容感兴趣),所以不考虑此信息。
根据xray_cv.date,2020年3月拍摄的最新照片有8张。这些图像被分离在一个列表中,从模型训练中删除。 所以,之后将用做最终模型的验证。
imgs_march = [ ‘2966893D-5DDF-4B68–9E2B-4979D5956C8E.jpeg’, ‘6C94A287-C059–46A0–8600-AFB95F4727B7.jpeg’, ‘F2DE909F-E19C-4900–92F5–8F435B031AC6.jpeg’, ‘F4341CE7–73C9–45C6–99C8–8567A5484B63.jpeg’, ‘E63574A7–4188–4C8D-8D17–9D67A18A1AFA.jpeg’, ‘31BA3780–2323–493F-8AED-62081B9C383B.jpeg’, ‘7C69C012–7479–493F-8722-ABC29C60A2DD.jpeg’, ‘B2D20576–00B7–4519-A415–72DE29C90C34.jpeg’ ]
下一步将构建指向训练数据集(xray_cv_train)的数据框,该数据框应引用134个图像(来自Covid的全部输入图像,用于稍后验证的图像除外):
xray_cv_train = xray_cv[~xray_cv.filename.isin(imgs_march)] xray_cv_train.reset_index(drop=True, inplace=True)
而最终的验证集(xray_cv_val )有8个图像:
xray_cv_val = xray_cv[xray_cv.filename.isin(imgs_march)] xray_cv_val.reset_index(drop=True, inplace=True)
要记住,在前一项中,只有数据框是使用从原始文件metada.csv中获取的信息建立的。咱们知道哪些图像要存储在最终的训练文件中,如今咱们须要“物理地”将实际图像(以数字化格式)分离到正确的子目录(文件夹)中。
为此,咱们将使用load_image_folder support()函数,该函数将元数据文件中引用的图像从一个文件复制到另外一个文件:
def load_image_folder(df_metadata, col_img_name, input_dataset_path, output_dataset_path): img_number = 0 # 对COVID-19的行进行循环 for (i, row) in df_metadata.iterrows(): imagePath = os.path.sep.join([input_dataset_path, row[col_img_name]]) if not os.path.exists(imagePath): print('image not found') continue filename = row[col_img_name].split(os.path.sep)[-1] outputPath = os.path.sep.join([f"{output_dataset_path}", filename]) shutil.copy2(imagePath, outputPath) img_number += 1 print('{} selected Images on folder {}:'.format(img_number, output_dataset_path))
按照如下说明,134个选定图像将被复制到文件夹../10_dataset/covid/。
input_dataset_path = '../input/10_Covid_images/images' output_dataset_path = '../dataset/covid' dataset = xray_cv_train col_img_name = 'filename' load_image_folder(dataset, col_img_name, input_dataset_path, output_dataset_path)
对于数据集2(正常和肺炎图像),不提供包含元数据的文件。所以,你只需将图像从输入文件复制到末尾。为此,咱们建立load_image_folder_direct()函数,该函数将许多图像(随机选择)从une文件夹复制到另外一个文件夹:
def load_image_folder_direct(input_dataset_path, output_dataset_path, img_num_select): img_number = 0 pathlist = Path(input_dataset_path).glob('**/*.*') nof_samples = img_num_select rc = [] for k, path in enumerate(pathlist): if k < nof_samples: rc.append(str(path)) # 路径不是字符串形式 shutil.copy2(path, output_dataset_path) img_number += 1 else: i = random.randint(0, k) if i < nof_samples: rc[i] = str(path) print('{} selected Images on folder {}:'.format(img_number, output_dataset_path))
在../input/20_Chest_Xray/train/NORMAL文件夹重复一样的过程,咱们将随机复制用于训练的相同数量的图像 (len (xray_cv_train)),134个图像。这样,用于训练模型的数据集是平衡的。
input_dataset_path = '../input/20_Chest_Xray/train/NORMAL' output_dataset_path = '../dataset/normal' img_num_select = len(xray_cv_train) load_image_folder_direct(input_dataset_path, output_dataset_path, img_num_select)
以一样的方式,咱们分离出20个随机图像,以供之后在模型验证中使用。
input_dataset_path = '../input/20_Chest_Xray/train/NORMAL' output_dataset_path = '../dataset_validation/normal_validation' img_num_select = 20 load_image_folder_direct(input_dataset_path, output_dataset_path, img_num_select)
尽管咱们没有用显示肺炎症状的图像(Covid-19)来训练模型,可是观察最终模型如何与肺炎症状一块儿工做是颇有趣的。所以,咱们还分离了其中的20幅图像,以供验证。
input_dataset_path = '../input/20_Chest_Xray/train/PNEUMONIA' output_dataset_path = '../dataset_validation/non_covid_pneumonia_validation' img_num_select = 20 load_image_folder_direct(input_dataset_path, output_dataset_path, img_num_select)
下面的图片显示了在这个步骤结束时应该如何配置文件夹(不管如何在个人Mac上)。此外,红色标记的数字显示文件夹中包含的x光图像的相应数量。
因为文件夹中的图像数量很少,所以能够对其进行可视化检查。为此,使用 plots_from_files():
def plots_from_files(imspaths, figsize=(10, 5), rows=1, titles=None, maintitle=None): """Plot the images in a grid""" f = plt.figure(figsize=figsize) if maintitle is not None: plt.suptitle(maintitle, fontsize=10) for i in range(len(imspaths)): sp = f.add_subplot(rows, ceildiv(len(imspaths), rows), i + 1) sp.axis('Off') if titles is not None: sp.set_title(titles[i], fontsize=16) img = plt.imread(imspaths[i]) plt.imshow(img) def ceildiv(a, b): return -(-a // b)
而后,定义将在训练中使用的数据集的路径和带有要查看的图像名称的列表:
dataset_path = '../10_dataset' normal_images = list(paths.list_images(f"{dataset_path}/normal")) covid_images = list(paths.list_images(f"{dataset_path}/covid"))
经过调用可视化支持函数,能够显示如下图像:
plots_from_files(covid_images, rows=10, maintitle="Covid-19 X-ray images")
plots_from_files(normal_images, rows=10, maintitle="Normal X-ray images")
图像看起来不错。
该模型的训练是使用预约义的图像进行的,应用了称为“迁移学习”的技术。
迁移学习是一种机器学习方法,其中为一个任务开发的模型被重用为第二个任务中模型的起点。
Keras应用程序是Keras的深度学习库模块,它为几种流行的架构(如VGG1六、ResNet50v二、ResNet101v二、Xception、MobileNet等)提供模型定义和预训练的权重。
使用的预训练模型是VGG16,由牛津大学视觉图形组(VGG)开发,并在论文“Very Deep Convolutional Networks for Large-Scale Image Recognition”中描述。除了在开发公共的图像分类模型时很是流行外,这也是Adrian博士在其教程中建议的模型。
理想的作法是使用几个模型(例如ResNet50v二、ResNet101v2)进行测试(基准测试),甚至建立一个特定的模型(如Zhang等人在论文中建议的模型:https://arxiv.org/pdf/2003.12338.pdf)。但因为这项工做的最终目标只是概念上的验证,因此咱们仅探索VGG16。
VGG16是一种卷积神经网络(CNN)体系结构,尽管在2014年已经开发出来,但今天仍然被认为是处理图像分类的最佳体系结构之一。
VGG16体系结构的一个特色是,它们没有大量的超参数,而是专一于卷积层上,卷积层上有一个3x3滤波器(核)和一个2x2最大池层。在整个体系结构中始终保持一组卷积和最大池化层。最后,该架构有2个FC(彻底链接的层)和softmax激活输出。
VGG16中的16表示架构中有16个带权重的层。该网络是巨大的,在使用全部原始16层的状况下,有将近1.4亿个训练参数。
在咱们的例子中,最后两层(FC1和FC2)是在本地训练的,参数总数超过1500万,其中大约有590000个参数是在本地训练的(而其他的是“frozen(冻结的)”)。
须要注意的第一点是,VNN16架构的第一层使用224x224x3的图像,所以咱们必须确保要训练的X光图像也具备这些维度,由于它们是卷积网络的“第一层”的一部分。所以,当使用原始权重(weights=“imagenet”)加载模型时,咱们不保留模型的顶层(include_top=False),而这些层将被咱们的层(headModel)替换。
baseModel = VGG16(weights="imagenet", include_top=False, input_tensor=Input(shape=(224, 224, 3)))
接下来,咱们必须定义用于训练的超参数(在下面的注释中,将测试一些可能的值以提升模型的“准确性”):
INIT_LR = 1e-3 # [0.0001] EPOCHS = 10 # [20] BS = 8 # [16, 32] NODES_DENSE0 = 64 # [128] DROPOUT = 0.5 # [0.0, 0.1, 0.2, 0.3, 0.4, 0.5] MAXPOOL_SIZE = (4, 4) # [(2,2) , (3,3)] ROTATION_DEG = 15 # [10] SPLIT = 0.2 # [0.1]
而后构建咱们的模型,将其添加到基础模型中:
headModel = baseModel.output headModel = AveragePooling2D(pool_size=MAXPOOL_SIZE)(headModel) headModel = Flatten(name="flatten")(headModel) headModel = Dense(NODES_DENSE0, activation="relu")(headModel) headModel = Dropout(DROPOUT)(headModel) headModel = Dense(2, activation="softmax")(headModel)
headModel被放置在基础模型之上,成为模型的一部分,进行实际训练(肯定最佳权重)。
model = Model(inputs=baseModel.input, outputs=headModel)
重要的是要记住一个预训练过的CNN模型,如VGG16,被训练了成千上万的图像来分类普通图像(如狗、猫、汽车和人)。咱们如今须要作的是根据咱们的须要定制它(分类X光图像)。理论上,模型的第一层简化了图像的部分,识别出其中的形状。这些初始标签很是通用(如直线、圆和正方形),所以咱们不用再训练它们。咱们只想训练网络的最后一层。
下面的循环在基础模型的全部层上执行,“冻结”它们,以便在第一个训练过程当中不会更新它们。
for layer in baseModel.layers: layer.trainable = False
此时,模型已经准备好接受训练,但首先,咱们必须为模型的训练准备数据(图像)。
咱们先建立一个包含存储图像的名称(和路径)的列表:
imagePaths = list(paths.list_images(dataset_path))
那么对于列表中的每一个图像,咱们必须:
提取图像标签(在本例中为covid或normal)
将BGR(CV2默认值)的图像通道设置为RGB
将图像大小调整为224x 224(VGG16的默认值)
data = [] labels = [] for imagePath in imagePaths: label = imagePath.split(os.path.sep)[-2] image = cv2.imread(imagePath) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (224, 224)) data.append(image) labels.append(label)
数据和标签被转换成数组,每一个像素的值从0到255改为从0到1,便于训练。
data = np.array(data) / 255.0 labels = np.array(labels)
标签将使用一个one-hot编码技术进行数字编码。
lb = LabelBinarizer() labels = lb.fit_transform(labels) labels = to_categorical(labels)
此时,训练数据集分为训练集和测试集(训练80%,测试20%):
(trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=SPLIT, stratify=labels, random_state=42)
最后但并不是最不重要的是,咱们应该应用“数据加强”技术。
如Chowdhury等人所建议。在他们的论文中,三种加强策略(旋转、缩放和平移)可用于为COVID-19生成额外的训练图像,有助于防止“过分拟合”:https://arxiv.org/pdf/2003.13145.pdf。
原始胸部X光图像(A),逆时针旋转45度后图像(B),顺时针旋转45度后图像,水平和垂直平移20%后图像(D),放大10%后图像(E)。
使用TS/Keras图像预处理库(ImageDataGenerator),能够更改多个图像参数,例如:
trainAug = ImageDataGenerator( rotation_range=15, width_shift_range=0.2, height_shift_range=0.2, rescale=1./255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest')
一开始,仅应用图像最大旋转15度来评估结果。
trainAug = ImageDataGenerator(rotation_range=ROTATION_DEG, fill_mode="nearest")
此时,咱们已经定义了模型和数据,并准备好进行编译和训练。
编译容许咱们给模型添加额外的特性,好比loss函数、优化器和度量。
对于网络训练,咱们使用损失函数来计算网络预测值与训练数据实际值之间的差别。伴随着优化器算法(如Adam)对网络中的权重进行更改。这些超参数有助于网络训练的收敛,使损失值尽量接近于零。
咱们还指定了优化器(lr)的学习率。在这种状况下,lr被定义为1e-3。若是在训练过程当中注意到“跳跃”的增长,即模型不能收敛,则应下降学习率,以达到最小值。
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS) model.compile(loss="binary_crossentropy", optimizer=opt, metrics=["accuracy"])
让咱们来训练模型:
H = model.fit( trainAug.flow(trainX, trainY, batch_size=BS), steps_per_epoch=len(trainX) // BS, validation_data=(testX, testY), validation_steps=len(testX) // BS, epochs=EPOCHS)
结果看起来已经至关有趣了,验证数据的精度达到了92%!绘制精度图表:
评估训练模型:
看看混淆矩阵:
[[27 0] [ 4 23]] acc: 0.9259 sensitivity: 1.0000 specificity: 0.8519
从用最初选择的超参数训练的模型中,咱们获得:
结果并不使人满意,由于15%没有Covid的患者会被误诊。咱们先对模型进行微调,更改一些超参数:
所以,咱们有:
INIT_LR = 0.0001 # 曾经是 1e-3 EPOCHS = 20 # 曾经是 10 BS = 16 # 曾经是 8 NODES_DENSE0 = 128 # 曾经是 64 DROPOUT = 0.5 MAXPOOL_SIZE = (2, 2) # 曾经是 (4, 4) ROTATION_DEG = 15 SPLIT = 0.2
结果
precision recall f1-score support covid 0.93 1.00 0.96 27 normal 1.00 0.93 0.96 27 accuracy 0.96 54 macro avg 0.97 0.96 0.96 54 weighted avg 0.97 0.96 0.96 54
以及混淆矩阵:
[[27 0] [ 2 25]] acc: 0.9630 sensitivity: 1.0000 specificity: 0.9259
结果好多了!如今具备93%的特异性,这意味着在没有COVID-19(即真反例)的患者中,在93%到100%的时间内咱们能够准确地将他们识别为“COVID-19阴性”。
目前看来,这个结果颇有但愿。让咱们保存这个模型,在那些没有通过训练的图像上测试(Covid-19的8个图像和从输入数据集中随机选择的20个图像)。
model.save("../model/covid_normal_model.h5")
首先,让咱们检索模型并显示最终的体系结构,以检查一切是否正常:
new_model = load_model('../model/covid_normal_model.h5') # 展现模型架构 new_model.summary()
这个模型看起来不错,是VGG16的16层结构。请注意,可训练参数为590210,这是最后两层的总和,它们被添加到参数为14.7M的预训练模型中。
让咱们验证测试数据集中加载的模型:
[INFO] evaluating network... precision recall f1-score support covid 0.93 1.00 0.96 27 normal 1.00 0.93 0.96 27 accuracy 0.96 54 macro avg 0.97 0.96 0.96 54 weighted avg 0.97 0.96 0.96 54
很好,咱们获得了与以前相同的结果,这意味着训练的模型被正确地保存和加载。如今让咱们用以前保存的8个Covid图像验证模型。为此,咱们建立了另一个函数,它是为单个图像测试开发的
def test_rx_image_for_Covid19(imagePath): img = cv2.imread(imagePath) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)) img = np.expand_dims(img, axis=0) img = np.array(img) / 255.0 pred = new_model.predict(img) pred_neg = round(pred[0][1]*100) pred_pos = round(pred[0][0]*100) print('\n X-Ray Covid-19 Detection using AI - MJRovai') print(' [WARNING] - Only for didactic purposes') if np.argmax(pred, axis=1)[0] == 1: plt.title('\nPrediction: [NEGATIVE] with prob: {}% \nNo Covid-19\n'.format( pred_neg), fontsize=12) else: plt.title('\nPrediction: [POSITIVE] with prob: {}% \nPneumonia by Covid-19 Detected\n'.format( pred_pos), fontsize=12) img_out = plt.imread(imagePath) plt.imshow(img_out) plt.savefig('../Image_Prediction/Image_Prediction.png') return pred_pos
在Notebook上,此函数将显示如下结果:
经过更改其他7个图像的imagePath值,咱们得到如下结果:
全部图像均呈阳性,确认100%灵敏度。
如今让咱们测试20个单独的图像,以验证标记为NORMAL的有效性。Notebook上的第一个应该是:
一个接一个的测试能够确认预测,可是因为咱们有更多的图像,让咱们使用另外一个函数来测试一组图像,一次完成: test_rx_image_for_Covid19_batch (img_lst) 。
让咱们建立包含在验证文件夹中的图像的列表:
validation_path = '../dataset_validation' normal_val_images = list(paths.list_images( f"{validation_path}/normal_validation")) non_covid_pneumonia_validation_images = list(paths.list_images( f"{validation_path}/non_covid_pneumonia_validation")) covid_val_images = list(paths.list_images( f"{validation_path}/covid_validation"))
test_rx_image_for_Covid19_batch (img_lst) 函数以下:
def test_rx_image_for_Covid19_batch(img_lst): neg_cnt = 0 pos_cnt = 0 predictions_score = [] for img in img_lst: pred, neg_cnt, pos_cnt = test_rx_image_for_Covid19_2(img, neg_cnt, pos_cnt) predictions_score.append(pred) print ('{} positive detected in a total of {} images'.format(pos_cnt, (pos_cnt+neg_cnt))) return predictions_score, neg_cnt, pos_cnt
将该函数应用于咱们先前分离的20幅图像:
img_lst = normal_val_images normal_predictions_score, normal_neg_cnt, normal_pos_cnt = test_rx_image_for_Covid19_batch(img_lst) normal_predictions_score
咱们观察到,全部20人被诊断为阴性,得分以下(记住,接近“1”表明“阳性”):
0.25851375, 0.025379542, 0.005824779, 0.0047603976, 0.042225637, 0.025087152, 0.035508618, 0.009078974, 0.014746706, 0.06489486, 0.003134642, 0.004970203, 0.15801577, 0.006775451, 0.0032735346, 0.007105667, 0.001369465, 0.005155371, 0.029973848, 0.014993184
只有2例图像的评估(1-准确度)低于90%(0.26和0.16)。
请记住,输入数据集/input/20_Chest_Xray/有两个文件夹,/train和/test。只有/train中的一部分图像用于训练,而且模型从未看到测试图像:
input - |_ 10_Covid_Imagens _ | |_ metadata.csv | |_ images [used train model 1] |_ 20_Chest_Xray - |_ test _ |_ NORMAL |_ PNEUMONIA |_ train _ |_ NORMAL [used train model 1] |_ PNEUMONIA
而后,咱们能够利用这个文件夹测试全部图像。首先,咱们建立了图像列表:
validation_path = '../input/20_Chest_Xray/test' normal_test_val_images = list(paths.list_images(f"{validation_path}/NORMAL")) print("Normal Xray Images: ", len(normal_test_val_images)) pneumo_test_val_images = list(paths.list_images(f"{validation_path}/PNEUMONIA")) print("Pneumo Xray Images: ", len(pneumo_test_val_images))
咱们观察了234张诊断为正常的“未公开”图片(还有390张不是由Covid-19引发的肺炎)。应用批处理函数,咱们观察到24幅图像出现假阳性(约10%)。让咱们看看模型输出值是如何分布的,记住函数返回的值计算以下:
pred = new_model.predict(image) pred_pos = round(pred[0][0] * 100)
咱们观察到,预测精度的平均值为0.15,而且很是集中于接近于零的值(中值仅为0.043)。有趣的是,大多数误报率接近0.5,少数异常值高于0.6。
除了改进模型外,还值得研究产生假阳性的图像。
因为输入数据集也有肺炎患者的X光图像,但不是由Covid引发的,因此让咱们应用模型1(Covid/Normal)来查看结果是什么:
结果很是糟糕,在390张图片中,185张有假阳性。而观察结果的分布,发现有一个峰值接近80%,也就是说,这是很是错误的!
回顾这一结果在技术上并不使人惊讶,由于该模型没有通过普通肺炎患者图像的训练。
无论怎样,这是一个大问题,由于我认为专家能够用肉眼区分病人是否患有肺炎。然而,也许更难区分这种肺炎是由Covid-19(SARS-CoV-2)、任何其余病毒,甚至是细菌引发的。
将Covid-19引发的肺炎患者与其余类型的病毒或细菌区分开来的模型更有 用。为此,另外一个模型将被训练,如今有感染Covid-19的病人和感染肺炎但不是由Covid-19病毒引发的病人的图像。
从个人GitHub下载Notebook放入subdirectory /notebooks目录:https://github.com/Mjrovai/covid19Xray/blob/master/10_X-Ray_Covid_development/notebooks/20_Xray_Pneumo_Covid19_Model_2_Training_Tests.ipynb。
导入使用的库并运行。
模型2中使用的Covid图像数据集与模型1中使用的相同,只是如今它存储在不一样的文件夹中。
dataset_path = '../20_dataset'
肺炎图像将从文件夹/input/20_Chest_Xray/train/PNEUMONIA/下载并存储在/20_dataset/pneumo/中。使用的函数与以前相同:
input_dataset_path = '../input/20_Chest_Xray/train/PNEUMONIA' output_dataset_path = '../20_dataset/pneumo' img_num_select = len(xray_cv_train) # 样本数量与Covid数据相同
这样,咱们调用可视化支持函数,检查获得的结果:
pneumo_images = list(paths.list_images(f"{dataset_path}/pneumo")) covid_images = list(paths.list_images(f"{dataset_path}/covid")) plots_from_files(covid_images, rows=10, maintitle="Covid-19 X-ray images")
plots_from_files(pneumo_images, rows=10, maintitle="Pneumony X-ray images"
图像看起来不错。
要使用的预训练模型是VGG16,与模型1训练相同
baseModel = VGG16(weights="imagenet", include_top=False, input_tensor=Input(shape=(224, 224, 3)))
接下来,咱们必须定义用于训练的超参数。咱们与模型1的参数相同:
INIT_LR = 0.0001 EPOCHS = 20 BS = 16 NODES_DENSE0 = 128 DROPOUT = 0.5 MAXPOOL_SIZE = (2, 2) ROTATION_DEG = 15 SPLIT = 0.2
而后,构建模型:
headModel = baseModel.output headModel = AveragePooling2D(pool_size=MAXPOOL_SIZE)(headModel) headModel = Flatten(name="flatten")(headModel) headModel = Dense(NODES_DENSE0, activation="relu")(headModel) headModel = Dropout(DROPOUT)(headModel) headModel = Dense(2, activation="softmax")(headModel)
将headModel模型放在最后,成为用于训练的真实模型。
model = Model(inputs=baseModel.input, outputs=headModel)
在基础模型的全部层上执行的如下循环将“冻结”它们,以便在第一个训练过程当中不会更新它们。
for layer in baseModel.layers: layer.trainable = False
此时,模型已经准备好接受训练,可是咱们应该首先为模型准备数据(图像)。
咱们先建立一个包含存储图像的名称(和路径)的列表,而后执行与模型1相同的预处理:
imagePaths = list(paths.list_images(dataset_path)) data = [] labels = [] for imagePath in imagePaths: label = imagePath.split(os.path.sep)[-2] image = cv2.imread(imagePath) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (224, 224)) data.append(image) labels.append(label) data = np.array(data) / 255.0 labels = np.array(labels)
标签使用one-hot。
lb = LabelBinarizer() labels = lb.fit_transform(labels) labels = to_categorical(labels)
此时,咱们将把训练数据集分为训练和测试(80%用于训练,20%用于测试):
(trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=SPLIT, stratify=labels, random_state=42)
最后,咱们将应用数据加强技术。
trainAug = ImageDataGenerator(rotation_range=ROTATION_DEG, fill_mode="nearest")
咱们已经定义了模型和数据,并准备好进行编译和训练。
编译:
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS) model.compile(loss="binary_crossentropy", optimizer=opt, metrics=["accuracy"])
训练:
H = model.fit( trainAug.flow(trainX, trainY, batch_size=BS), steps_per_epoch=len(trainX) // BS, validation_data=(testX, testY), validation_steps=len(testX) // BS, epochs=EPOCHS)
使用20个阶段和初始参数,结果看起来很是有趣,验证数据的精度达到100%!让咱们绘制精度图表,评估训练的模型,并查看混淆矩阵:
precision recall f1-score support covid 0.96 1.00 0.98 27 pneumo 1.00 0.96 0.98 27 accuracy 0.98 54 macro avg 0.98 0.98 0.98 54 weighted avg 0.98 0.98 0.98 54
混淆矩阵
[[27 0] [ 1 26]] acc: 0.9815 sensitivity: 1.0000 specificity: 0.9630
经过训练模型(初始选择超参数),咱们获得:
结果彻底使人满意,由于只有4%的患者没有Covid会被误诊。但与本例同样,肺炎患者和Covid-19患者之间的正确分类是最有益处的;咱们至少应该对超参数进行一些调整,再次进行训练。
第一件事,我试图下降最初的lr一点点,这是一场灾难。因此我恢复了原值。
我还减小了数据的分割,稍微增长了Covid图像,并将最大旋转角度更改成10度,这是在与原始数据集相关的论文中建议的:
INIT_LR = 0.0001 EPOCHS = 20 BS = 16 NODES_DENSE0 = 128 DROPOUT = 0.5 MAXPOOL_SIZE = (2, 2) ROTATION_DEG = 10 SPLIT = 0.1
所以,咱们有:
precision recall f1-score support covid 1.00 1.00 1.00 13 pneumo 1.00 1.00 1.00 14 accuracy 1.00 27 macro avg 1.00 1.00 1.00 27 weighted avg 1.00 1.00 1.00 27
以及混淆矩阵:
[[13 0] [ 0 14]] acc: 1.0000 sensitivity: 1.0000 specificity: 1.0000
结果看起来更好,但咱们使用了不多的测试数据!让咱们保存模型,并像之前同样用大量图像对其进行测试。
model.save("../model/covid_pneumo_model.h5")
咱们观察到390张标记为非Covid-19引发的肺炎的图像。应用批测试功能,咱们发现总共390张图片中只有3张出现假阳性(约0.8%)。此外,预测精度值的平均值为0.04,而且很是集中于接近于零的值(中值仅为0.02)。
总的结果甚至比之前的模型所观察到的还要好。有趣的是,几乎全部的结果都在前3个四分位以内,只有不多的异常值有超过20%的偏差。
在这种状况下,还值得研究产生假阳性的图像(仅3幅)。
因为输入数据集也有正常患者(未经训练)的X光图像,让咱们应用模型2(Covid/普通肺炎)看看结果如何
在这种状况下,结果并无模型1测试中看到的那么糟糕,在234幅图像中,有45幅出现了假阳性(19%)。
好吧,理想状况是对每种状况使用正确的模型,可是若是只使用一种,那么模型2是正确的选择。
注:在最后一次测试中,我作了一个基准测试,我尝试改变加强参数,正如Chowdhury等人所建议的,令我惊讶的是,结果并很差。
对于web应用的开发,咱们使用Flask,这是一个用Python编写的web微框架。它被归类为微框架,由于它不须要特定的工具或库来运行。
此外,咱们只须要几个库和与单独测试图像相关的函数。因此,让咱们首先在一个干净的Notebook上工做,在那里使用已经训练和保存的模型2执行测试。
如今只导入测试前一个Notebook中建立的模型所需的库。
import numpy as np import cv2 from tensorflow.keras.models import load_model
def test_rx_image_for_Covid19_2(model, imagePath): img = cv2.imread(imagePath) img_out = img img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)) img = np.expand_dims(img, axis=0) img = np.array(img) / 255.0 pred = model.predict(img) pred_neg = round(pred[0][1]*100) pred_pos = round(pred[0][0]*100) if np.argmax(pred, axis=1)[0] == 1: prediction = 'NEGATIVE' prob = pred_neg else: prediction = 'POSITIVE' prob = pred_pos cv2.imwrite('../Image_Prediction/Image_Prediction.png', img_out) return prediction, prob
covid_pneumo_model = load_model('../model/covid_pneumo_model.h5')
而后,从上传一些图像,并确认一切正常:
imagePath = '../dataset_validation/covid_validation/6C94A287-C059-46A0-8600-AFB95F4727B7.jpeg' test_rx_image_for_Covid19_2(covid_pneumo_model, imagePath)
结果是:(‘POSITIVE’, 96.0)
imagePath = '../dataset_validation/normal_validation/IM-0177–0001.jpeg' test_rx_image_for_Covid19_2(covid_pneumo_model, imagePath)
结果是: (‘NEGATIVE’, 99.0)
imagePath = '../dataset_validation/non_covid_pneumonia_validation/person63_bacteria_306.jpeg' test_rx_image_for_Covid19_2(covid_pneumo_model, imagePath)
结果是:(‘NEGATIVE’, 98.0)
到目前为止,全部的开发都是在Jupyter Notebook上完成的,咱们应该作最后的测试,让代码做为python脚本运行在最初建立的开发目录中,名称为:covidXrayApp_test.py。
# 导入库和设置 import numpy as np import cv2 from tensorflow.keras.models import load_model # 关闭信息和警告 import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' def test_rx_image_for_Covid19_2(model, imagePath): img = cv2.imread(imagePath) img_out = img img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)) img = np.expand_dims(img, axis=0) img = np.array(img) / 255.0 pred = model.predict(img) pred_neg = round(pred[0][1]*100) pred_pos = round(pred[0][0]*100) if np.argmax(pred, axis=1)[0] == 1: prediction = 'NEGATIVE' prob = pred_neg else: prediction = 'POSITIVE' prob = pred_pos cv2.imwrite('./Image_Prediction/Image_Prediction.png', img_out) return prediction, prob # 载入模型 covid_pneumo_model = load_model('./model/covid_pneumo_model.h5') # --------------------------------------------------------------- # 执行测试 imagePath = './dataset_validation/covid_validation/6C94A287-C059-46A0-8600-AFB95F4727B7.jpeg' prediction, prob = test_rx_image_for_Covid19_2(covid_pneumo_model, imagePath) print (prediction, prob)
让咱们直接在终端上测试脚本:
一切工做完美
第一步是从一个新的Python环境开始。为此,使用Terminal定义一个工做目录(covid19XrayWebApp),而后在那里用Python建立一个环境
mkdir covid19XrayWebApp cd covid19XrayWebApp conda create --name covid19xraywebapp python=3.7.6 -y conda activate covid19xraywebapp
进入环境后,安装Flask和运行应用程序所需的全部库:
conda install -c anaconda flask conda install -c anaconda requests conda install -c anaconda numpy conda install -c conda-forge matplotlib conda install -c anaconda pillow conda install -c conda-forge opencv pip install --upgrade pip pip install tensorflow pip install gunicorn
建立必要的子目录:
[here the app.py] model [here the trained and saved model] templates [here the .html file] static _ [here the .css file and static images] |_ xray_analysis [here the output image after analysis] |_ xray_img [here the input x-ray image]
从个人GitHub复制文件并将其存储在新建立的目录中
Githhub:https://github.com/Mjrovai/covid19Xray/tree/master/20_covid19XrayWebApp
执行如下步骤
在服务器上负责后端执行的python应用程序称为app.py,必须位于主目录的根目录下
在/template中,应该存储index.html文件,该文件将是应用程序的前端
在/static将是style.css文件,负责前端(template.html)的样式。
在/static下,还有接收待分析图像的子目录,以及分析结果(其原始名称以及诊断和准确性百分比)。
全部文件安装到正确的位置后,工做目录以下所示:
将文件安装到文件夹中后,运行app.py,这是咱们的web应用程序的“引擎”,负责接收存储在用户计算机的图像。
python app.py
在终端咱们能够观察到:
在浏览器上,输入:
应用程序将在你的本地网络中运行:
咱们能够选择启动一个Covid的X光图像,它已经在开发过程当中用于验证。
步骤顺序以下:
对其中一张有肺炎但没有Covid-19的图片重复测试:
正如导言中所讨论的,本项目是一个概念证实,证实了在X光图像中检测Covid-19的病毒的可行性。要使项目在实际状况中使用,还必须完成几个步骤。如下是一些建议:
与健康领域的专业人员一块儿验证整个项目
寻找最佳的预训练模型
使用从患者身上得到的图像训练模型,患者最好是来自应用程序将要使用的同一区域。
使用Covid-19获取更普遍的患者图像集
改变模型的超参数
测试用3个类标(正常、Covid和肺炎)训练模型的可行性
更改应用程序,容许选择更适合使用的模型(模型1或模型2)
本文中使用的全部代码均可以Github仓库下载:https://github.com/Mjrovai/covid19Xray。
欢迎关注磐创AI博客站:
http://panchuang.net/
sklearn机器学习中文官方文档:
http://sklearn123.com/
欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/