深度學習檢測瘧疾


**  磐創 AI 分享**

作者 | Tommy
編譯 | VK
來源 | Towards Data Science

在這個項目中,我們將通過美國國立衛生研究院提供的一個數據集,從 150 名感染了惡性瘧原蟲寄生蟲的患者身上獲取 27558 張不同的細胞圖像,並與 50 名健康患者的細胞圖像混合,這些圖像可以通過這裏的鏈接下載: https://www.kaggle.com/iarunava/cell-images-for-detecting-malaria

我們的任務是建立一個機器學習 / 深度學習算法,能夠對檢測到的細胞是否被寄生蟲感染進行分類。

在這個項目中,我們將使用一種深度學習算法,即卷積神經網絡 (Convolutional Neural Network, CNN) 算法,它通過訓練來分類某個細胞的圖像是否被感染。由於這是一個超過 330mb 數據的大項目,我建議將其應用到 jupiter Notebook 中。

首先,我們需要導入必要的起始庫:

import pandas as pd
import numpy as np
import os
import cv2
import random

你之前可能使用 panda.read_csv() 導入 csv 格式的數據集,但是,由於所有數據都是 png 格式的,因此可能需要使用 os 和 cv2 庫以不同的方式導入它們。

OS 是一個強大的 Python 庫,允許你與操作系統進行交互,無論操作系統是 Windows、Mac OS 還是 Linux。

另一方面,cv2 是一個專門設計用於解決各種計算機視覺問題,如讀取和加載圖像。

首先,我們需要設置一個變量來設置我們的路徑,因爲我們以後會繼續使用它:

root = '../Malaria/cell_images/'
in = '/Parasitized/'
un = '/Uninfected/'

因此,從上面的路徑是名爲 “瘧疾(Malaria)” 的文件夾是我的細胞圖像文件夾文件存儲的地方,這也是寄生和未感染細胞的圖像存儲的地方。這樣,我們就能更容易地操縱這些路徑。

現在讓我們操作系統列表目錄 (“path”) 函數列出被寄生和未受感染文件夾中的所有圖像,如下所示:

Parasitized = os.listdir(root+in)
Uninfected = os.listdir(root+un)

使用 OpenCV 和 Matplotlib 顯示圖像

例如,與 csv 文件不同,我們只能使用 pandas 的函數 head 列出多個數據,df.head(10) 顯示 10 行數據,對於圖像,我們需要使用 for 循環和 matplotlib 庫來顯示數據。

因此,首先讓我們導入 matplotlib 併爲每個寄生和未感染的細胞圖像繪製圖像,如下所示:

import matplotlib.pyplot as plt

然後繪製圖像:

plt.figure(figsize = (12,24))
for i in range(4):
    plt.subplot(1, 4, i+1)
    img = cv2.imread(root+in+ Parasitized[i])
    plt.imshow(img)
    plt.title('PARASITIZED : 1')
    plt.tight_layout()
plt.show()

plt.figure(figsize = (12,24))
for i in range(4):
    plt.subplot(2, 4, i+1)
    img = cv2.imread(root+un+ Uninfected[i+1])
    plt.imshow(img)
    plt.title('UNINFECTED : 0')
    plt.tight_layout()
plt.show()

因此,爲了繪製圖像,我們需要 matplotlib 中的 figure 函數,然後我們需要根據 figsize 函數確定每個圖形的大小。

如上所示,其中第一個數字是寬度,第二個數字是高度。然後在 for 循環中,我們將使用 i 作爲變量來迭代 range() 中指示的次數。

在本例中,我們將只顯示 4 個圖像。隨後,我們將使用 library 中的 subplot 函數來指示某個迭代的行數、列數和繪圖數,我們使用 i+1 來實現。

由於我們已經創建了子圖,但是子圖仍然是空的,因此,我們需要使用 OpenCV 庫,通過使用 cv2.imread() 函數導入圖像,幷包括其中的路徑和變量 i,這樣它將繼續循環到下一張圖片,直到提供的最大範圍爲止。

最後,我們將 cv2 庫導入的圖像用 plt.imshow() 函數繪出,我們將獲得以下輸出:

將圖像和標籤分配到變量中

接下來,我們要將所有圖像 (無論是寄生細胞圖像還是未感染細胞圖像) 及其標籤插入到一個變量中,其中 1 表示寄生細胞,0 表示未感染細胞。因此,首先,我們需要爲圖像和標籤創建一個空變量。但在此之前,我們需要使用 Keras 的 img_to_array()函數。

from tensorflow.keras.preprocessing.image import img_to_array

假設存儲圖像的變量稱爲 data,存儲標籤的變量稱爲 labels,那麼我們可以執行以下代碼:

data = []
labels = []

for img in Parasitized:
    try:
        img_read = plt.imread(root+in+ img)
        img_resize = cv2.resize(img_read, (100, 100))
        img_array = img_to_array(img_resize)
        data.append(img_array)
        labels.append(1)
    except:
        None

for img in Uninfected:
    try:
        img_read = plt.imread(root+un+ img)
        img_resize = cv2.resize(img_read, (100, 100))
        img_array = img_to_array(img_resize)
        data.append(img_array)
        labels.append(0)
    except:
        None

在爲空數組賦值變量數據和標籤之後,我們需要使用 for 循環插入每一張圖像。

這裏有一些不同之處,for 循環中還包含了 try 和 except。try 用於在 for 循環中正常運行代碼,另一方面,except 用於代碼遇到錯誤或崩潰時,以便代碼可以繼續循環。

與上面的圖像顯示代碼類似,我們可以使用 plt.imread(“path”) 函數,但這一次,我們不需要顯示任何子圖。

你可能想知道我們爲什麼用 plt.imread() 而不是 cv2.imread()。它們的功能是一樣的,事實上,還有很多其他的圖像讀取庫,例如,Pillow 庫的 image.open() 或 Scikit-Image 庫的 io.imread()。

然而,OpenCV 以 BGR 或藍、綠、紅的順序讀取圖像,這就是爲什麼上面顯示的圖像是藍色的,儘管實際的圖片是粉色的。因此,我們需要使用以下代碼將其轉換回 RGB 順序:

cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

另一方面,Matplotlib、Scikit Image 和 Pillow Image reading 函數會自動按 RGB 順序讀取圖像,因此,對於我們的單元格圖像實際顏色,我們不需要再進行轉換:

plt.imshow(data[0])
plt.show()

之後,我們可以通過使用 OpenCV 的 cv2.resize() 函數來調整圖像的大小,以將加載的圖像設置爲一個特定的高度和寬度,如上面所示的 100 width 和 100 height。

接下來,因爲我們的圖像是抽象格式的,我們以後將無法對其進行訓練、測試或將其插入變量中,因此,我們需要使用 keras 的 img_to_array() 函數將其轉換爲數組格式。這樣,我們就可以使用. append() 函數將每個圖像插入變量的方括號中,該函數用於在不改變原始狀態的情況下將對象插入數組的最後一個列表。

所以在我們的循環中,我們不斷地在每個循環中添加一個新的圖像。

預處理數據

不像機器學習項目,我們可以立即分割我們的數據,在深入學習,特別是神經網絡,以減少方差,並減少過擬合。在 Python 中,有許多方法可以隨機化數據,比如使用 Sklearn,如下所示:

from sklearn.utils import shuffle

或使用如下所示的隨機方法:

from random import shuffle

但在這個項目中,我們將使用 Numpy 的隨機函數。因此,我們需要將數組轉換成 Numpy 的數組函數,然後對圖像數據進行隨機化,如下所示:

image_data = np.array(data)
labels = np.array(labels)

idx = np.arange(image_data.shape[0])
np.random.shuffle(idx)
image_data = image_data[idx]
labels = labels[idx]

首先,我們將數據和標籤變量轉換爲 Numpy 格式。然後,我們可以使用 np.arange() 然後使用 np.random.shuffle() 功能。最後,我們將被隨機數據重新分配到它們的原始變量中,以確保被隨機的數據被保存。

用於訓練、測試和驗證的拆分數據

在數據被隨機之後,我們應該通過導入必要的庫將它們拆分爲訓練、測試和驗證標籤和數據,如下所示:

from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split

然後我們設置一個函數,將數據轉換爲 32 位數據以保存:

def prep_dataset(X,y):

    X_prep = X.astype('float32')/255
    y_prep = to_categorical(np.array(y))
    return (X_prep, y_prep)

然後通過使用 Sklearn 庫,我們可以將數據分爲訓練、測試和驗證:

X_tr, X_ts, Y_tr, Y_ts = train_test_split(image_data,labels, test_size=0.15, shuffle=True,stratify=labels,random_state=42)
X_ts, X_val, Y_ts, Y_val = train_test_split(X_ts,Y_ts, test_size=0.5, stratify=Y_ts,random_state=42)

X_tr, Y_tr = prep_dataset(X_tr,Y_tr)
X_val, Y_val = prep_dataset(X_val,Y_val)
X_ts, _ = prep_dataset(X_ts,Y_ts)

由於存在驗證數據,因此需要執行兩次拆分。在 spliting 函數中,我們需要在前兩個參數中分配數據和標籤,然後告訴它們被劃分成的百分比,隨機狀態是爲了確保它們產生的數據總是以一致的順序排列。

卷積神經網絡模型的建立

在建立 CNN 模型之前,讓我們從理論上對它有一點更深入的瞭解。CNN 主要應用於圖像分類,自 1998 年發明以來,由於其高性能和高精度,一直受到人們的歡迎。那麼它是如何工作的呢?

正如我們從上面的插圖中看到的,CNN 對圖像進行分類,取圖像的一部分,並將其通過幾層卷積處理來分類圖像。從這幾個層中,可以將它們分爲三種類型的層,分別是卷積層、池化層和全連接層。

卷積層

第一層,卷積層是 CNN 最重要的方面之一,它的名字來源於此。其目的是利用核函數從輸入圖像中提取特徵。該核將使用點積連續掃描輸入圖像,創建一個新的分析層,稱爲特徵圖 / 激活圖。其機理如下:

21 + 42 + 93 + 2(-4) + 17 + 44 + 12 + 1(-5) + 2*1 = 51

然後繼續,直到所有特徵圖的單元格都被填滿。

池化層

在我們創建了特徵圖之後,我們將應用一個池化層來減小其大小以減少過擬合。此步驟中有幾個操作,但是,最流行的技術是最大池,其中特徵圖的掃描區域將僅取最高值,如下圖所示:

卷積和池的步驟可以重複,直到理想的大小是最後敲定,然後我們可以繼續分類部分,它被稱爲全連接層。

全連通層

在這一階段,變換後的特徵圖將被展平爲一個列向量,該列向量將在每個迭代過程中經過前饋神經網絡和反向傳播過程,持續數個階段。最後,CNN 模型將區分主要特徵和非主要特徵,利用 Softmax 分類對圖像進行分類。

在 Python 中,我們需要從 Keras 庫導入 CNN 函數:

from tensorflow.keras import models, layers
from tensorflow.keras.callbacks import EarlyStopping

然後,我們將構建我們自己的 CNN 模型,包括 4 個卷積層和池化層:

model = models.Sequential()

#Input + Conv 1 + ReLU + Max Pooling
model.add(layers.Conv2D(32,(5,5),activation='relu',padding='same',input_shape=X_tr.shape[1:]))

model.add(layers.MaxPool2D(strides=4))
model.add(layers.BatchNormalization())

# Conv 2 + ReLU + Max Pooling
model.add(layers.Conv2D(64,(5,5),padding='same',activation='relu'))

model.add(layers.MaxPool2D(strides=2))
model.add(layers.BatchNormalization())

# Conv 3 + ReLU + Max Pooling
model.add(layers.Conv2D(128,(3,3),padding='same',activation='relu'))

model.add(layers.MaxPool2D(strides=2))
model.add(layers.BatchNormalization())

# Conv 4 + ReLU + Max Pooling
model.add(layers.Conv2D(256,(3,3),dilation_rate=(2,2),padding='same',activation='relu'))
model.add(layers.Conv2D(256,(3,3),activation='relu'))
model.add(layers.MaxPool2D(strides=2))
model.add(layers.BatchNormalization())

# Fully Connected + ReLU
model.add(layers.Flatten())

model.add(layers.Dense(300, activation='relu'))
model.add(layers.Dense(100, activation='relu'))

#Output
model.add(layers.Dense(2, activation='softmax'))

model.summary()

Keras 中的卷積層

好的,在這段代碼中,你可能會感到困惑的第一件事是 model .sequential()。

利用 Keras 構建深度學習模型有兩種選擇,一種是序列模型,另一種是函數模型。他們兩人之間的差異是序列模型只允許一層一層地建立一個模型, 另一方面, 函數模型允許層連接到前一層, 多層, 甚至任何層, 你想建立一個更復雜的模型。由於我們正在構建一個簡單的 CNN 模型,所以我們將使用序列模型。

我們之前瞭解到,CNN 由卷積層組成,卷積層後來使用池化層進行簡化,因此,我們需要使用 Keras 函數 model.add() 添加層,然後添加我們喜歡的層。

由於我們的圖像是 2D 形式,我們只需要 2D 卷積層,同樣使用卷積層的 Keras 函數:layers.Conv2D(). 正如你在每一層所看到的,第一層有一個數字 32,第二層有一個數字 64,乘以 2 並以此類推。這叫做濾波器。它所做的是試圖捕捉圖像的模式。

因此, 隨着濾波器尺寸的增大, 我們可以在較小的圖像上捕獲更多的模式, 儘管沒有太多理論的證明, 然而, 這被認爲是最優方法。

接下來,(5,5)和 (3,3) 矩陣是我們上面討論的用於創建特徵圖的核。然後這裏有兩個有趣的部分:ReLu 的激活和填充。那些是什麼?我們先來討論 ReLu。它是由 Rectified Linear Unit 縮短而來的,其想法是採用更簡單的模型,提高訓練過程的效率。

下一個問題是填充。如我們所知,卷積層不斷減小圖像的大小,因此,如果我們在新的特徵圖的所有四個邊上添加填充,我們將保持相同的大小。所以我們在處理後的圖像周圍添加新的單元格,值爲 0,以保持圖像的大小。填充有兩個選項:相同或零。接下來就是 input_shape,它只是我們的輸入圖像,只在第一層中用於輸入我們的訓練數據。

Keras 中的池化層

讓我們轉到池化層,我們可以使用 Keras 的 layers.MaxPool2D()。這裏有一種東西叫做跨步 (stride)。它是核將在輸入上每次移動的步數。因此,如果我們用 5x5 的核,並且跨步爲 4,核將計算圖像最左邊的部分,然後跳過右邊的 4 個單元格來執行下一次計算。

你可能會注意到,每個卷積層和池化層都以批標準化結束。爲什麼?因爲在每一層中,都有不同的分佈,訓練過程會變慢,因爲它需要適應每一層。但是,如果強制所有層具有相似的分佈,則可以跳過此步驟並提高訓練速度。這就是爲什麼我們在每一層中應用批標準化,通過如下所示的 4 個步驟對每一層中的輸入進行標準化:

Keras 全連接層

現在我們已經到了 CNN 的最後階段,也就是全連接的階段。但在進入這個階段之前,由於我們將在全連接階段使用 “Dense” 層,所以我們需要使用 Keras 層將處理過的數據平鋪成一維。Flatten()函數用於將垂直和水平的數據合併到單個列中。

我們將數據展平後,現在需要使用 layers.Dense() 函數將它們全連接起來, 然後指定要用作輸出的神經元數量,考慮到當前的神經元是 1024 個。所以我們先使用 300 個神經元,然後是 100 個神經元,並使用 ReLu 對其進行優化。

最後,我們到達輸出階段,這也是通過使用 Dense 函數來完成的,然而,由於我們的分類只有 2 個概率,即感染瘧疾或未感染瘧疾,因此,我們將輸出單位設置爲 2。此外,在分類任務中,我們在輸出層使用 Softmax 激活而不是 ReLu,因爲 ReLu 將所有負類設置爲零。

綜上所述,我們構建的模型如下:

然後,我們將把我們的訓練和驗證數據整合到模型中:

model.compile(optimizer='adam',
             loss='categorical_crossentropy',
             metrics=['accuracy'])

es = EarlyStopping(monitor='val_accuracy',mode='max',patience=3,verbose=1)

history= model.fit(X_tr,Y_tr,
                 epochs=20,
                 batch_size=50,
                 validation_data=(X_val,Y_val),
                 callbacks=[es])

由於數據量很大,這個過程需要一些時間。如下圖所示,每個 epoch 平均運行 9 分鐘。

評估

我們可以通過繪製評估圖來評估模型性能,但在此之前,我們需要導入 seaborn 庫:

import seaborn as sns

然後將準確度和損失繪製如下:

fig, ax=plt.subplots(2,1,figsize=(12,10))
fig.suptitle('Train evaluation')

sns.lineplot(ax= ax[0],x=np.arange(0,len(history.history['accuracy'])),y=history.history['accuracy'])
sns.lineplot(ax= ax[0],x=np.arange(0,len(history.history['accuracy'])),y=history.history['val_accuracy'])

ax[0].legend(['Train','Validation'])
ax[0].set_title('Accuracy')

sns.lineplot(ax= ax[1],x=np.arange(0,len(history.history['loss'])),y=history.history['loss'])
sns.lineplot(ax= ax[1],x=np.arange(0,len(history.history['loss'])),y=history.history['val_loss'])

ax[1].legend(['Train','Validation'])
ax[1].set_title('Loss')

plt.show()

這將給我們一個輸出:

它的平均性能似乎超過 90%,這是非常好的,正如 CNN 模型所預期的。不過,爲了讓我們更具體地瞭解數據性能,讓我們使用 Sklearn 構建一個混淆矩陣,並預測輸出:

from sklearn.metrics import confusion_matrix, accuracy_score

Y_pred = model.predict(X_ts)

Y_pred = np.argmax(Y_pred, axis=1)

conf_mat = confusion_matrix(Y_ts,Y_pred)

sns.set_style(style='white')
plt.figure(figsize=(12,8))
heatmap = sns.heatmap(conf_mat,vmin=np.min(conf_mat.all())vmax=np.max(conf_mat)annot=True,fmt='d'annot_kws={"fontsize":20},cmap='Spectral')
heatmap.set_title('Confusion Matrix Heatmap\n¿Is the cell infected?'fontdict={'fontsize':15}pad=12)
heatmap.set_xlabel('Predicted',fontdict={'fontsize':14})
heatmap.set_ylabel('Actual',fontdict={'fontsize':14})
heatmap.set_xticklabels(['NO','YES']fontdict={'fontsize':12})
heatmap.set_yticklabels(['NO','YES']fontdict={'fontsize':12})
plt.show()

print('-Acuracy achieved: {:.2f}%\n-Accuracy by model was: {:.2f}%\n-Accuracy by validation was: {:.2f}%'.
      format(accuracy_score(Y_ts,Y_pred)*100,(history.history['accuracy'][-1])*100,(history.history['val_accuracy'][-1])*100))

這將爲我們提供以下準確度:

錯誤樣本

讓我們看看錯誤示例的樣子:

index=0
index_errors= []

for label, predict in zip(Y_ts,Y_pred):
    if label != predict:
        index_errors.append(index)
    index +=1

plt.figure(figsize=(20,8))

for i,img_index in zip(range(1,17),random.sample(index_errors,k=16)):
    plt.subplot(2,8,i)
    plt.imshow(np.reshape(255*X_ts[img_index](100,100,3)))
    plt.title('Actual: '+str(Y_ts[img_index])+' Predict: '+str(Y_pred[img_index]))
plt.show()

輸出:

儘管未感染,但錯誤的預測圖像似乎包含了細胞上的幾個紫色斑塊,因此,模型實際預測它們爲感染細胞是可以理解的。

我想說 CNN 是一個非常強大的圖像分類模型,不需要做很多預處理任務,因爲處理包含在卷積層和池化層中。

希望通過本文的學習,讓你能夠利用卷積神經網絡進行圖像分類。

感謝你的閱讀。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Pb_ZVwuWMi_uK8QT-kFmKw