作者|Muhammad Ardi
编译|Flin
起源|analyticsvidhya

介绍

嘿!几个小时前我刚刚实现一个深度学习我的项目,当初我想分享一下我所做的事件。这一挑战的指标是确定一个人是否患有肺炎。如果是,则确定是否由细菌或病毒引起。好吧,我感觉这个我的项目应该叫做分类而不是检测。

换句话说,此工作将是一个多分类问题,其中标签名称为:normal(失常),virus(病毒)和bacteria(细菌)。为了解决这个问题,我将应用CNN(卷积神经网络),它具备杰出的图像分类能力,。不仅如此,在这里我还实现了图像增强技术,以进步模型性能。顺便说一句,我取得了80%的测试数据准确性,这对我来说是十分令人印象粗浅的。

能够从该Kaggle链接下载此我的项目中应用的数据集。

  • https://www.kaggle.com/paulti...

整个数据集自身的大小约为1 GB,因而下载可能须要一段时间。或者,咱们也能够间接创立一个Kaggle Notebook并在那里编码整个我的项目,因而咱们甚至不须要下载任何内容。接下来,如果浏览数据集文件夹,你将看到有3个子文件夹,即train,test和val。

好吧,我认为这些文件夹名称是不言自明的。此外,train文件夹中的数据别离包含失常,病毒和细菌类别的1341、1345和2530个样本。我想这就是我介绍的全部内容了,当初让咱们进入代码的编写!

留神:我在本文结尾处搁置了该我的项目中应用的全副代码。

加载模块和训练图像

应用计算机视觉我的项目时,要做的第一件事是加载所有必须的模块和图像数据自身。我应用tqdm模块显示进度条,稍后你将看到它有用的起因。

我最初导入的是来自Keras模块的ImageDataGenerator。该模块将帮忙咱们在训练过程中施行图像增强技术。

import osimport cv2import pickleimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsfrom tqdm import tqdmfrom sklearn.preprocessing import OneHotEncoderfrom sklearn.metrics import confusion_matrixfrom keras.models import Model, load_modelfrom keras.layers import Dense, Input, Conv2D, MaxPool2D, Flattenfrom keras.preprocessing.image import ImageDataGeneratornp.random.seed(22)

接下来,我定义两个函数以从每个文件夹加载图像数据。乍一看,上面的两个性能可能看起来齐全一样,然而在应用粗体显示的行上实际上存在一些差别。这样做是因为NORMAL和PNEUMONIA文件夹中的文件名构造略有不同。只管有所不同,但两个性能执行的其余过程基本相同。

首先,将所有图像调整为200 x 200像素。

这一点很重要,因为所有文件夹中的图像都有不同的尺寸,而神经网络只能承受具备固定数组大小的数据。

接下来,基本上所有图像都存储有3个色彩通道,这对X射线图像来说是多余的。因而,我的想法是将这些彩色图像都转换为灰度图像。

# Do not forget to include the last slashdef load_normal(norm_path):    norm_files = np.array(os.listdir(norm_path))    norm_labels = np.array(['normal']*len(norm_files))        norm_images = []    for image in tqdm(norm_files):        image = cv2.imread(norm_path + image)        image = cv2.resize(image, dsize=(200,200))        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)        norm_images.append(image)            norm_images = np.array(norm_images)        return norm_images, norm_labelsdef load_pneumonia(pneu_path):    pneu_files = np.array(os.listdir(pneu_path))    pneu_labels = np.array([pneu_file.split('_')[1] for pneu_file in pneu_files])        pneu_images = []    for image in tqdm(pneu_files):        image = cv2.imread(pneu_path + image)        image = cv2.resize(image, dsize=(200,200))        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)        pneu_images.append(image)            pneu_images = np.array(pneu_images)        return pneu_images, pneu_labels

申明了以上两个函数后,当初咱们能够应用它来加载训练数据了。如果你运行上面的代码,你还将看到为什么我抉择在该我的项目中实现tqdm模块。

norm_images, norm_labels = load_normal('/kaggle/input/chest-xray-pneumonia/chest_xray/train/NORMAL/')pneu_images, pneu_labels = load_pneumonia('/kaggle/input/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/')

到目前为止,咱们曾经取得了几个数组:norm_images,norm_labels,pneu_images和pneu_labels。

带_images后缀的示意它蕴含预处理的图像,而带_labels后缀的数组示意它存储了所有根本信息(也称为标签)。换句话说,norm_images和pneu_images都将成为咱们的X数据,其余的将成为y数据。

为了使我的项目看起来更简略,我将这些数组的值连接起来并存储在X_train和y_train数组中。

X_train = np.append(norm_images, pneu_images, axis=0)y_train = np.append(norm_labels, pneu_labels)

顺便说一句,我应用以下代码获取每个类的图像数:

显示多张图像

好吧,在这个阶段,显示几个图像并不是强制性的。但我想做是为了确保图片是否曾经加载和预处理好。上面的代码用于显示14张从X_train阵列随机拍摄的图像以及标签。

fig, axes = plt.subplots(ncols=7, nrows=2, figsize=(16, 4))indices = np.random.choice(len(X_train), 14)counter = 0for i in range(2):    for j in range(7):        axes[i,j].set_title(y_train[indices[counter]])        axes[i,j].imshow(X_train[indices[counter]], cmap='gray')        axes[i,j].get_xaxis().set_visible(False)        axes[i,j].get_yaxis().set_visible(False)        counter += 1plt.show()

咱们能够看到上图,所有图像当初都具备完全相同的大小,这与我用于本帖子封面图片的图像不同。

加载测试图像

咱们曾经晓得所有训练数据都已胜利加载,当初咱们能够应用完全相同的函数加载测试数据。步骤简直雷同,然而这里我将那些加载的数据存储在X_test和y_test数组中。用于测试的数据自身蕴含624个样本。

norm_images_test, norm_labels_test = load_normal('/kaggle/input/chest-xray-pneumonia/chest_xray/test/NORMAL/')pneu_images_test, pneu_labels_test = load_pneumonia('/kaggle/input/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/')X_test = np.append(norm_images_test, pneu_images_test, axis=0)y_test = np.append(norm_labels_test, pneu_labels_test)

此外,我留神到仅加载整个数据集就须要很长时间。因而,我将应用pickle模块将X_train,X_test,y_train和y_test保留在独自的文件中。这样我下次想再应用这些数据的时候,就不须要再次运行这些代码了。

# Use this to save variableswith open('pneumonia_data.pickle', 'wb') as f:    pickle.dump((X_train, X_test, y_train, y_test), f)# Use this to load variableswith open('pneumonia_data.pickle', 'rb') as f:    (X_train, X_test, y_train, y_test) = pickle.load(f)

因为所有X数据都通过了很好的预处理,因而当初应用标签y_train和y_test了。

标签预处理

此时,两个y变量都由以字符串数据类型编写的失常,细菌或病毒组成。实际上,这样的标签只是神经网络所不能承受的。因而,咱们须要将其转换为繁多格局。

侥幸的是,咱们从Scikit-Learn模块获取了 OneHotEncoder对象,它对实现转换十分有帮忙。为此,咱们须要先在y_train和y_test上创立一个新轴。(咱们创立了这个新轴,因为那是OneHotEncoder冀望的形态)。

y_train = y_train[:, np.newaxis]y_test = y_test[:, np.newaxis]

接下来,像这样初始化one_hot_encoder。请留神,在这里我将False作为稠密参数传递,以便简化下一步。然而,如果你想应用稠密矩阵,则只需应用sparse = True或将参数保留为空即可。

one_hot_encoder = OneHotEncoder(sparse=False)

最初,咱们将应用one_hot_encoder将这些y数据转换为one-hot。而后将编码后的标签存储在y_train_one_hot和y_test_one_hot中。这两个数组是咱们将用于训练的标签。

y_train_one_hot = one_hot_encoder.fit_transform(y_train)y_test_one_hot = one_hot_encoder.transform(y_test)

将数据X重塑为(None,200,200,1)

当初让咱们回到X_train和X_test。重要的是要晓得这两个数组的形态别离为(5216、200、200)和(624、200、200)。

乍一看,这两个形态看起来还能够,因为咱们能够应用plt.imshow()函数进行显示。然而,这种形态卷积层不可承受,因为它心愿将一个色彩通道作为其输出。

因而,因为该图像实质上是灰度图像,因而咱们须要增加一个1维的新轴,该轴将被卷积层辨认为惟一的色彩通道。尽管它的实现并不像我的解释那么简单:

X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], X_test.shape[2], 1)

运行上述代码后,如果咱们同时查看X_train和X_test的形态,那么咱们将看到当初的形态别离是(5216,200,200,1)和(624,200,200,1)。

数据裁减

减少数据(或者更具体地说是减少训练数据)的要点是,咱们将通过创立更多的样本(每个样本都具备某种随机性)来减少用于训练的数据数量。这些随机性可能包含平移、旋转、缩放、剪切和翻转。

这种技术能够帮忙咱们的神经网络分类器缩小过拟合,或者说,它能够使模型更好地泛化数据样本。侥幸的是,因为存在能够从Keras模块导入的ImageDataGenerator对象,实现非常简单。

datagen = ImageDataGenerator(        rotation_range = 10,          zoom_range = 0.1,         width_shift_range = 0.1,         height_shift_range = 0.1)

因而,我在下面的代码中所做的基本上是设置随机范畴。如果你想理解每个参数的详细信息,请点击这里链接到ImageDataGenerator的文档。

  • https://keras.io/api/preproce...

接下来,在初始化datagen对象之后,咱们须要做的是使它和咱们的X_train相匹配。而后,该过程被随后施加的flow()的办法,该步骤中是十分有用的,使得所述 train_gen对象当初可能产生加强数据的批次。

datagen.fit(X_train)train_gen = datagen.flow(X_train, y_train_one_hot, batch_size=32)

CNN(卷积神经网络)

当初是时候真正构建神经网络架构了。让咱们从输出层(input1)开始。因而,这一层基本上会获取X数据中的所有图像样本。因而,咱们须要确保第一层承受与图像尺寸完全相同的形态。值得注意的是,咱们仅须要定义(宽度,高度,通道),而不是(样本,宽度,高度,通道)。

尔后,此输出层连贯到几对卷积池层对,而后最终连贯到全连贯层。请留神,因为ReLU的计算速度比S型更快,因而模型中的所有暗藏层都应用ReLU激活函数,因而所需的训练工夫更短。最初,要连贯的最初一层是output1,它由3个具备softmax激活函数的神经元组成。

这里应用softmax是因为咱们心愿输入是每个类别的概率值。

input1 = Input(shape=(X_train.shape[1], X_train.shape[2], 1))cnn = Conv2D(16, (3, 3), activation='relu', strides=(1, 1),     padding='same')(input1)cnn = Conv2D(32, (3, 3), activation='relu', strides=(1, 1),     padding='same')(cnn)cnn = MaxPool2D((2, 2))(cnn)cnn = Conv2D(16, (2, 2), activation='relu', strides=(1, 1),     padding='same')(cnn)cnn = Conv2D(32, (2, 2), activation='relu', strides=(1, 1),     padding='same')(cnn)cnn = MaxPool2D((2, 2))(cnn)cnn = Flatten()(cnn)cnn = Dense(100, activation='relu')(cnn)cnn = Dense(50, activation='relu')(cnn)output1 = Dense(3, activation='softmax')(cnn)model = Model(inputs=input1, outputs=output1)

在应用下面的代码结构了神经网络之后,咱们能够通过对model对象利用summary()来显示模型的摘要。上面是咱们的CNN模型的详细情况。咱们能够看到咱们总共有800万个参数——这的确很多。好吧,这就是为什么我在Kaggle Notebook上运行这个代码。

总之,在构建模型之后,咱们须要应用分类穿插熵损失函数和Adam优化器来编译神经网络。应用这个损失函数,因为它只是多类分类工作中罕用的函数。同时,我抉择Adam作为优化器,因为它是在大多数神经网络工作中最小化损失的最佳抉择。

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

当初是时候训练模型了!在这里,咱们将应用fit_generator()而不是fit(),因为咱们将从train_gen对象获取训练数据。如果你关注数据裁减局部,你会留神到train_gen是应用X_train和y_train_one_hot创立的。因而,咱们不须要在fit_generator()办法中显式定义X-y对。

history = model.fit_generator(train_gen, epochs=30,           validation_data=(X_test, y_test_one_hot))

train_gen的非凡之处在于,训练过程中将应用具备肯定随机性的样本来实现。因而,咱们在X_train中领有的所有训练数据都不会间接输出到神经网络中。取而代之的是,这些样本将被用作生成器的根底,通过一些随机变换生成一个新图像。

此外,该生成器在每个期间产生不同的图像,这对于咱们的神经网络分类器更好地泛化测试集中的样本十分无利。上面是训练的过程。

Epoch 1/30163/163 [==============================] - 19s 114ms/step - loss: 5.7014 - acc: 0.6133 - val_loss: 0.7971 - val_acc: 0.7228...Epoch 10/30163/163 [==============================] - 18s 111ms/step - loss: 0.5575 - acc: 0.7650 - val_loss: 0.8788 - val_acc: 0.7308...Epoch 20/30163/163 [==============================] - 17s 102ms/step - loss: 0.5267 - acc: 0.7784 - val_loss: 0.6668 - val_acc: 0.7917...Epoch 30/30163/163 [==============================] - 17s 104ms/step - loss: 0.4915 - acc: 0.7922 - val_loss: 0.7079 - val_acc: 0.8045

整个训练自身在我的Kaggle Notebook上破费了大概10分钟。所以要急躁点!经过训练后,咱们能够绘制出准确度得分的进步和损失值的升高,如下所示:

plt.figure(figsize=(8,6))plt.title('Accuracy scores')plt.plot(history.history['acc'])plt.plot(history.history['val_acc'])plt.legend(['acc', 'val_acc'])plt.show()plt.figure(figsize=(8,6))plt.title('Loss value')plt.plot(history.history['loss'])plt.plot(history.history['val_loss'])plt.legend(['loss', 'val_loss'])plt.show()

依据下面的两个图,咱们能够说,即便在这30个期间内测试准确性和损失值都在稳定,模型的性能仍在一直进步。

这里要留神的另一重要事件是,因为咱们在我的项目的晚期利用了数据加强办法,因而该模型不会蒙受过拟合的困扰。咱们在这里能够看到,在最终迭代中,训练和测试数据的准确性别离为79%和80%。

乏味的事实:在施行数据加强办法之前,我在训练数据上取得了100%的准确性,在测试数据上取得了64%的准确性,这显然是过拟合了。因而,咱们能够在此处分明地看到,减少训练数据对于进步测试准确性得分十分无效,同时也能够缩小过拟合。

模型评估

当初,让咱们深刻理解应用混同矩阵得出的测试数据的准确性。首先,咱们须要预测所有X_test并将后果从独热格局转换回其理论的分类标签。

predictions = model.predict(X_test)predictions = one_hot_encoder.inverse_transform(predictions)

接下来,咱们能够像这样应用confusion_matrix()函数:

cm = confusion_matrix(y_test, predictions)

重要的是要留神函数中应用的参数是(理论值,预测值)。该混同矩阵函数的返回值是一个二维数组,用于存储预测散布。为了使矩阵更易于解释,咱们能够应用Seaborn模块中的heatmap()函数进行显示。顺便说一句,这里的类名列表的值是依据one_hot_encoder.categories_返回的程序获取的。

classnames = ['bacteria', 'normal', 'virus']plt.figure(figsize=(8,8))plt.title('Confusion matrix')sns.heatmap(cm, cbar=False, xticklabels=classnames, yticklabels=classnames, fmt='d', annot=True, cmap=plt.cm.Blues)plt.xlabel('Predicted')plt.ylabel('Actual')plt.show()

依据下面的混同矩阵,咱们能够看到45张病毒X射线图像被预测为细菌。这可能是因为很难辨别这两种肺炎。然而,至多因为咱们对242个样本中的232个进行了正确分类,所以咱们的模型至多可能很好地预测由细菌引起的肺炎。

这就是整个我的项目!谢谢浏览!上面是运行整个我的项目所需的所有代码。

import osimport cv2import pickle    # Used to save variablesimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsfrom tqdm import tqdm    # Used to display progress barfrom sklearn.preprocessing import OneHotEncoderfrom sklearn.metrics import confusion_matrixfrom keras.models import Model, load_modelfrom keras.layers import Dense, Input, Conv2D, MaxPool2D, Flattenfrom keras.preprocessing.image import ImageDataGenerator    # Used to generate imagesnp.random.seed(22)# Do not forget to include the last slashdef load_normal(norm_path):    norm_files = np.array(os.listdir(norm_path))    norm_labels = np.array(['normal']*len(norm_files))        norm_images = []    for image in tqdm(norm_files):        # Read image        image = cv2.imread(norm_path + image)        # Resize image to 200x200 px        image = cv2.resize(image, dsize=(200,200))        # Convert to grayscale        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)        norm_images.append(image)            norm_images = np.array(norm_images)        return norm_images, norm_labelsdef load_pneumonia(pneu_path):    pneu_files = np.array(os.listdir(pneu_path))    pneu_labels = np.array([pneu_file.split('_')[1] for pneu_file in pneu_files])        pneu_images = []    for image in tqdm(pneu_files):        # Read image        image = cv2.imread(pneu_path + image)        # Resize image to 200x200 px        image = cv2.resize(image, dsize=(200,200))        # Convert to grayscale        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)        pneu_images.append(image)            pneu_images = np.array(pneu_images)        return pneu_images, pneu_labelsprint('Loading images')# All images are stored in _images, all labels are in _labelsnorm_images, norm_labels = load_normal('/kaggle/input/chest-xray-pneumonia/chest_xray/train/NORMAL/')pneu_images, pneu_labels = load_pneumonia('/kaggle/input/chest-xray-pneumonia/chest_xray/train/PNEUMONIA/')# Put all train images to X_train X_train = np.append(norm_images, pneu_images, axis=0)# Put all train labels to y_trainy_train = np.append(norm_labels, pneu_labels)print(X_train.shape)print(y_train.shape)# Finding out the number of samples of each classprint(np.unique(y_train, return_counts=True))print('Display several images')fig, axes = plt.subplots(ncols=7, nrows=2, figsize=(16, 4))indices = np.random.choice(len(X_train), 14)counter = 0for i in range(2):    for j in range(7):        axes[i,j].set_title(y_train[indices[counter]])        axes[i,j].imshow(X_train[indices[counter]], cmap='gray')        axes[i,j].get_xaxis().set_visible(False)        axes[i,j].get_yaxis().set_visible(False)        counter += 1plt.show()print('Loading test images')# Do the exact same thing as what we have done on train datanorm_images_test, norm_labels_test = load_normal('/kaggle/input/chest-xray-pneumonia/chest_xray/test/NORMAL/')pneu_images_test, pneu_labels_test = load_pneumonia('/kaggle/input/chest-xray-pneumonia/chest_xray/test/PNEUMONIA/')X_test = np.append(norm_images_test, pneu_images_test, axis=0)y_test = np.append(norm_labels_test, pneu_labels_test)# Save the loaded images to pickle file for future usewith open('pneumonia_data.pickle', 'wb') as f:    pickle.dump((X_train, X_test, y_train, y_test), f)# Here's how to load itwith open('pneumonia_data.pickle', 'rb') as f:    (X_train, X_test, y_train, y_test) = pickle.load(f)print('Label preprocessing')# Create new axis on all y datay_train = y_train[:, np.newaxis]y_test = y_test[:, np.newaxis]# Initialize OneHotEncoder objectone_hot_encoder = OneHotEncoder(sparse=False)# Convert all labels to one-hoty_train_one_hot = one_hot_encoder.fit_transform(y_train)y_test_one_hot = one_hot_encoder.transform(y_test)print('Reshaping X data')# Reshape the data into (no of samples, height, width, 1), where 1 represents a single color channelX_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], X_test.shape[2], 1)print('Data augmentation')# Generate new images with some randomnessdatagen = ImageDataGenerator(        rotation_range = 10,          zoom_range = 0.1,         width_shift_range = 0.1,         height_shift_range = 0.1)datagen.fit(X_train)train_gen = datagen.flow(X_train, y_train_one_hot, batch_size = 32)print('CNN')# Define the input shape of the neural networkinput_shape = (X_train.shape[1], X_train.shape[2], 1)print(input_shape)input1 = Input(shape=input_shape)cnn = Conv2D(16, (3, 3), activation='relu', strides=(1, 1),     padding='same')(input1)cnn = Conv2D(32, (3, 3), activation='relu', strides=(1, 1),     padding='same')(cnn)cnn = MaxPool2D((2, 2))(cnn)cnn = Conv2D(16, (2, 2), activation='relu', strides=(1, 1),     padding='same')(cnn)cnn = Conv2D(32, (2, 2), activation='relu', strides=(1, 1),     padding='same')(cnn)cnn = MaxPool2D((2, 2))(cnn)cnn = Flatten()(cnn)cnn = Dense(100, activation='relu')(cnn)cnn = Dense(50, activation='relu')(cnn)output1 = Dense(3, activation='softmax')(cnn)model = Model(inputs=input1, outputs=output1)model.compile(loss='categorical_crossentropy',               optimizer='adam', metrics=['acc'])# Using fit_generator() instead of fit() because we are going to use data# taken from the generator. Note that the randomness is changing# on each epochhistory = model.fit_generator(train_gen, epochs=30,           validation_data=(X_test, y_test_one_hot))# Saving modelmodel.save('pneumonia_cnn.h5')print('Displaying accuracy')plt.figure(figsize=(8,6))plt.title('Accuracy scores')plt.plot(history.history['acc'])plt.plot(history.history['val_acc'])plt.legend(['acc', 'val_acc'])plt.show()print('Displaying loss')plt.figure(figsize=(8,6))plt.title('Loss value')plt.plot(history.history['loss'])plt.plot(history.history['val_loss'])plt.legend(['loss', 'val_loss'])plt.show()# Predicting test datapredictions = model.predict(X_test)print(predictions)predictions = one_hot_encoder.inverse_transform(predictions)print('Model evaluation')print(one_hot_encoder.categories_)classnames = ['bacteria', 'normal', 'virus']# Display confusion matrixcm = confusion_matrix(y_test, predictions)plt.figure(figsize=(8,8))plt.title('Confusion matrix')sns.heatmap(cm, cbar=False, xticklabels=classnames, yticklabels=classnames, fmt='d', annot=True, cmap=plt.cm.Blues)plt.xlabel('Predicted')plt.ylabel('Actual')plt.show()

参考文献

JędrzejDudzicz对胸部X线查看的肺炎检出率约为92%

  • https://www.kaggle.com/jedrze...

Kerian ImageDataGenerator和Adrian Rosebrock的数据加强

  • https://www.pyimagesearch.com...

原文链接:https://www.analyticsvidhya.c...

欢送关注磐创AI博客站:
http://panchuang.net/

sklearn机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/