檢測X光圖像中Covid-19

做者|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

X光

近年來,基於計算機斷層掃描(CT)的機器學習在COVID-19診斷中的應用取得了一些有但願的成果。儘管這些方法取得了成功,但事實仍然是,COVID-19傳播在各類規模的社區。web

X光機更便宜、更簡單、操做更快,所以比CT更適合在更貧困或更偏遠地區工做的醫療專業人員。算法

目標

對抗Covid-19的一個重大挑戰是檢測病毒在人體內的存在。所以,本項目的目標是使用掃描的胸部X光圖像自動檢測肺炎患者(甚至無症狀或非病人)中Covid-19的病毒。這些圖像通過預處理,用於卷積神經網絡(CNN)模型的訓練。flask

CNN類型的網絡一般須要一個普遍的數據集才能正常工做。可是,在這個項目中,應用了一種稱爲「遷移學習」的技術,在數據集很小的狀況下很是有用(例如Covid-19中患者的圖像)。後端

目的是開發兩種分類模型:

  1. Covid-19的檢測與胸片檢測正常的比較

  2. Covid-19的檢測與肺炎患者的檢測的比較

按冠狀病毒相關論文定義的,全部類型的肺炎(COVID-19病毒引發的除外)僅被認爲是「肺炎」,並用Pneumo標籤(肺炎)分類。

咱們使用TensorFlow 2.0的模型、工具、庫和資源,這是一個開源平臺,用於機器學習,或者更準確地說,用於深度學習。最後在Flask中開發了一個web應用程序(web app),用於在接近現實的狀況下進行測試。

下圖爲咱們提供了最終應用程序如何工做的基本概念:

X光掃描胸部圖像(User_A.png),應用程序將圖像存儲在web應用程序的計算機上,決定圖像是否屬於受病毒污染的人(模型預測:[陽性]或[陰性])。在這兩種狀況下,應用程序都會通知預測的準確性(模型準確度:X%)。

爲了不二者都出錯,將向用戶顯示原始文件的名稱及其圖像。圖像的新副本存儲在本地,其名稱添加一個預測標籤,而且加上準確度。

這項工做分爲四個部分:

  1. 環境設置、數據清洗和準備

  2. 模型1訓練(Covid/正常)

  3. 模型2訓練(Covid/肺炎)

  4. 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,他根據羅斯布魯克博士的工做,進一步提出瞭如何測試結果模型的想法。

第1部分-環境設置和數據準備

數據集

訓練模型以從圖像中檢測任何類型的信息的第一個挑戰是要使用的數據量。 原則上,可公開獲取的圖像數量越多越好,可是請記住,這種流行病只有幾個月的歷史,因此對於Covid-19檢測項目來講,狀況並不是如此)

可是,Hall等人的研究,論文:https://arxiv.org/pdf/2004.02060.pdf,證實使用遷移學習技術僅用幾百幅圖像就能夠得到使人鼓舞的結果。

如引言所述,訓練兩個模型;所以,須要3組數據:

  1. 確認Covid-19的X光圖像集
  2. 常規(「正常」)患者的X光圖像集
  3. 一組顯示肺炎但不是由Covid-19引發的X光圖像

爲此,將下載兩個數據集:

數據集1:COVID-19的圖像集

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。

數據集2:肺炎和正常人的胸片

論文: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型視圖,因此這是用於訓練模型的視圖選擇類型。

定義用於訓練DL模型的環境

理想的作法是從一個新的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/下(保持原始測試和訓練結構)。

第2部分-模型1-Covid/正常

數據準備

構建Covid標籤數據集

從輸入數據集(/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)

爲COVID訓練圖像建立文件

要記住,在前一項中,只有數據框是使用從原始文件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))

那麼對於列表中的每一個圖像,咱們必須:

  1. 提取圖像標籤(在本例中爲covid或normal)

  2. 將BGR(CV2默認值)的圖像通道設置爲RGB

  3. 將圖像大小調整爲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

從用最初選擇的超參數訓練的模型中,咱們獲得:

  1. 100%的sensitivity(敏感度),也就是說,對於COVID-19陽性(即真正例)的患者,咱們能夠在100%的時間內準確地將其識別爲「COVID-19陽性」。
  2. 85%的specificity(特異性)意味着在沒有COVID-19(即真反例)的患者中,咱們只能在85%的時間內準確地將其識別爲「COVID-19陰性」。

結果並不使人滿意,由於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。

除了改進模型外,還值得研究產生假陽性的圖像。

測試不是由Covid引發的肺炎圖像

因爲輸入數據集也有肺炎患者的X光圖像,但不是由Covid引發的,因此讓咱們應用模型1(Covid/Normal)來查看結果是什麼:

結果很是糟糕,在390張圖片中,185張有假陽性。而觀察結果的分佈,發現有一個峯值接近80%,也就是說,這是很是錯誤的!

回顧這一結果在技術上並不使人驚訝,由於該模型沒有通過普通肺炎患者圖像的訓練。

無論怎樣,這是一個大問題,由於我認爲專家能夠用肉眼區分病人是否患有肺炎。然而,也許更難區分這種肺炎是由Covid-19(SARS-CoV-2)、任何其餘病毒,甚至是細菌引發的。

將Covid-19引發的肺炎患者與其餘類型的病毒或細菌區分開來的模型更有 用。爲此,另外一個模型將被訓練,如今有感染Covid-19的病人和感染肺炎但不是由Covid-19病毒引發的病人的圖像。

第3部分-模型2-Covid/普通肺炎

數據準備

  1. 從個人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. 導入使用的庫並運行。

模型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"

圖像看起來不錯。

預訓練CNN模型及其超參數的選擇

要使用的預訓練模型是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")

咱們已經定義了模型和數據,並準備好進行編譯和訓練。

模式2的編譯和訓練

編譯:

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

經過訓練模型(初始選擇超參數),咱們獲得:

  1. 100%敏感度,也就是說,對於COVID-19陽性(即真正例)的患者,咱們能夠在100%的時間內準確地肯定他們爲「COVID-19陽性」。
  2. 96%特異性,也就是說,在沒有COVID-19(即真反例)的患者中,咱們能夠在96%的時間內準確地將其識別爲「COVID-19陰性」。

結果徹底使人滿意,由於只有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等人所建議的,令我驚訝的是,結果並很差。

第4部分-Web應用程序

測試Python獨立腳本

對於web應用的開發,咱們使用Flask,這是一個用Python編寫的web微框架。它被歸類爲微框架,由於它不須要特定的工具或庫來運行。

此外,咱們只須要幾個庫和與單獨測試圖像相關的函數。因此,讓咱們首先在一個乾淨的Notebook上工做,在那裏使用已經訓練和保存的模型2執行測試。

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)

讓咱們直接在終端上測試腳本:

一切工做完美

建立在Flask中運行的環境

第一步是從一個新的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

執行如下步驟

  1. 在服務器上負責後端執行的python應用程序稱爲app.py,必須位於主目錄的根目錄下

  2. 在/template中,應該存儲index.html文件,該文件將是應用程序的前端

  3. 在/static將是style.css文件,負責前端(template.html)的樣式。

  4. 在/static下,還有接收待分析圖像的子目錄,以及分析結果(其原始名稱以及診斷和準確性百分比)。

全部文件安裝到正確的位置後,工做目錄以下所示:

在本地網絡上啓動Web應用

將文件安裝到文件夾中後,運行app.py,這是咱們的web應用程序的「引擎」,負責接收存儲在用戶計算機的圖像。

python app.py

在終端咱們能夠觀察到:

在瀏覽器上,輸入:

http://127.0.0.1:5000/

應用程序將在你的本地網絡中運行:

使用真實圖像測試web應用程序

咱們能夠選擇啓動一個Covid的X光圖像,它已經在開發過程當中用於驗證。

步驟順序以下:

對其中一張有肺炎但沒有Covid-19的圖片重複測試:

建議

正如導言中所討論的,本項目是一個概念證實,證實了在X光圖像中檢測Covid-19的病毒的可行性。要使項目在實際狀況中使用,還必須完成幾個步驟。如下是一些建議:

  1. 與健康領域的專業人員一塊兒驗證整個項目

  2. 尋找最佳的預訓練模型

  3. 使用從患者身上得到的圖像訓練模型,患者最好是來自應用程序將要使用的同一區域。

  4. 使用Covid-19獲取更普遍的患者圖像集

  5. 改變模型的超參數

  6. 測試用3個類標(正常、Covid和肺炎)訓練模型的可行性

  7. 更改應用程序,容許選擇更適合使用的模型(模型1或模型2)

本文中使用的全部代碼均可以Github倉庫下載:https://github.com/Mjrovai/covid19Xray。

原文連接:https://towardsdatascience.com/applying-artificial-intelligence-techniques-in-the-development-of-a-web-app-for-the-detection-of-9225f0225b4

歡迎關注磐創AI博客站:
http://panchuang.net/

sklearn機器學習中文官方文檔:
http://sklearn123.com/

歡迎關注磐創博客資源彙總站:
http://docs.panchuang.net/

相關文章
相關標籤/搜索