作者|Marcelo Rovai
編譯|VK
來源|Towards Data Science
免責聲明
本研究是為X光圖像中COVID-19的自動檢測而開發的,完全是為了教育目的。由於COVID-19沒有經過專業或學術評估,最終的應用並不打算成為一個准確的用於診斷人類的COVID-19的診斷系統,。
介紹
Covid-19是由一種病毒(SARS-CoV-2冠狀病毒)引起的大流行性疾病,已經感染了數百萬人,在幾個月內造成數十萬人死亡。
據世界衛生組織(WHO)稱,大多數COVID-19患者(約80%)可能無症狀,約20%的患者可能因為呼吸困難而需要住院治療。在這些病例中,大約5%可能需要支持來治療呼吸衰竭(通氣支持),這種情況可能會使重症監護設施崩潰。抗擊這一流行病的關鍵是快速檢測病毒攜帶者的方法。
冠狀病毒
冠狀病毒是引起呼吸道感染的病毒家族。這種新的冠狀病毒病原體是在中國登記病例后於1919年底發現。 它會導致一種名為冠狀病毒(COVID-19)的疾病。
1937年首次分離出人冠狀病毒。然而,直到1965年,這種病毒才被描述為冠狀病毒,因為它在顯微鏡下的輪廓看起來像一個樹冠。在下面的視頻中,你可以看到SARS-CoV-2病毒的原子級三維模型:
X光
近年來,基於計算機斷層掃描(CT)的機器學習在COVID-19診斷中的應用取得了一些有希望的成果。盡管這些方法取得了成功,但事實仍然是,COVID-19傳播在各種規模的社區。
X光機更便宜、更簡單、操作更快,因此比CT更適合在更貧困或更偏遠地區工作的醫療專業人員。
目標
對抗Covid-19的一個重大挑戰是檢測病毒在人體內的存在。因此,本項目的目標是使用掃描的胸部X光圖像自動檢測肺炎患者(甚至無症狀或非病人)中Covid-19的病毒。這些圖像經過預處理,用於卷積神經網絡(CNN)模型的訓練。
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,他根據羅斯布魯克博士的工作,進一步提出了如何測試結果模型的想法。
第1部分-環境設置和數據准備
數據集
訓練模型以從圖像中檢測任何類型的信息的第一個挑戰是要使用的數據量。 原則上,可公開獲取的圖像數量越多越好,但是請記住,這種流行病只有幾個月的歷史,所以對於Covid-19檢測項目來說,情況並非如此)
但是,Hall等人的研究,論文:https://arxiv.org/pdf/2004.02060.pdf,證明使用遷移學習技術僅用幾百幅圖像就可以獲得令人鼓舞的結果。
如引言所述,訓練兩個模型;因此,需要3組數據:
- 確認Covid-19的X光圖像集
- 常規(“正常”)患者的X光圖像集
- 一組顯示肺炎但不是由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/正常
數據准備
-
從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后,導入庫並運行支持函數。
構建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的深度學習庫模塊,它為幾種流行的架構(如VGG16、ResNet50v2、ResNet101v2、Xception、MobileNet等)提供模型定義和預訓練的權重。
使用的預訓練模型是VGG16,由牛津大學視覺圖形組(VGG)開發,並在論文“Very Deep Convolutional Networks for Large-Scale Image Recognition”中描述。除了在開發公共的圖像分類模型時非常流行外,這也是Adrian博士在其教程中建議的模型。
理想的做法是使用幾個模型(例如ResNet50v2、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
從用最初選擇的超參數訓練的模型中,我們得到:
- 100%的sensitivity(敏感度),也就是說,對於COVID-19陽性(即真正例)的患者,我們可以在100%的時間內准確地將其識別為“COVID-19陽性”。
- 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/普通肺炎
數據准備
-
從我的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"
圖像看起來不錯。
預訓練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
通過訓練模型(初始選擇超參數),我們得到:
- 100%敏感度,也就是說,對於COVID-19陽性(即真正例)的患者,我們可以在100%的時間內准確地確定他們為“COVID-19陽性”。
- 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執行測試。
-
現在只導入測試前一個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)
讓我們直接在終端上測試腳本:
一切工作完美
創建在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
執行以下步驟
-
在服務器上負責后端執行的python應用程序稱為app.py,必須位於主目錄的根目錄下
-
在/template中,應該存儲index.html文件,該文件將是應用程序的前端
-
在/static將是style.css文件,負責前端(template.html)的樣式。
-
在/static下,還有接收待分析圖像的子目錄,以及分析結果(其原始名稱以及診斷和准確性百分比)。
所有文件安裝到正確的位置后,工作目錄如下所示:
在本地網絡上啟動Web應用
將文件安裝到文件夾中后,運行app.py,這是我們的web應用程序的“引擎”,負責接收存儲在用戶計算機的圖像。
python app.py
在終端我們可以觀察到:
在瀏覽器上,輸入:
應用程序將在你的本地網絡中運行:
使用真實圖像測試web應用程序
我們可以選擇啟動一個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/