目录
* 本篇文章仅作为深度学习学习用途 而非商用
* 运行系统 :MacOS / Windows
* Python版本 :Python3.0
* 运行平台:Visual Studio Code
🤯
在已有的数据集下,训练一个CNN模型,预测一张CT图的肺部照片是否患有肺炎,若是患有肺炎,是因为细菌还是病毒引起的。数据集共有三个子文件夹:train / test / val 他们的作用相信各位都已经很清楚了 字面意思 这里不再多加赘述。
接近80%的Accuracy,验证集和训练集的拟合程度较好(并未达到最好,赠机epoch或者更换学习函数可能可以更好的训练模型,这里只迭代30次(呜呜CPU的痛 各位如果有GPU可以适当增肌迭代次数)
在执行深度学习项目时,要做的第一件事就是加载所有必需的模块和图像数据本身。
在这里qdm模块的作用是来显示进度条。
- import os
- import cv2
- import pickle
- import numpy as np
- import matplotlib.pyplot as plt
- import seaborn as sns
- from tqdm import tqdm
- from sklearn.preprocessing import OneHotEncoder
- from sklearn.metrics import confusion_matrix
- from keras.models import Model, load_model
- from keras.layers import Dense, Input, Conv2D, MaxPool2D, Flatten
- from keras.preprocessing.image import ImageDataGenerator
- np.random.seed(22)
以下两个函数来从每个文件夹加载图像数据。
首先,所有图像的大小都将调整为200 x 200像素。这很重要,因为所有文件夹中的图像都具有不同的维度,而神经网络只能接受具有固定阵列大小的数据。接下来,默认情况所有图像都存储在3个彩色通道中,但本次试验我们所用的数据集的图片均为灰度图,因此,这里需要将所有这些彩色图像转换为灰度。( 很重要的点!)
其中的这句话:
if image == '.DS_Store' : continue
如果您是Mac电脑,需要加上,Windows电脑的删去即可,当然不删也不会有任何的影响。
- def 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
- if image == '.DS_Store' : continue
- 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_labels
-
- def 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
- if image == '.DS_Store' : continue
- 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_labels
- norm_images, norm_labels = load_normal('/Users/liqun/Desktop/KS/MyPython/DataSet/chest_xray/train/NORMAL/')
- pneu_images, pneu_labels = load_pneumonia('/Users/liqun/Desktop/KS/MyPython/DataSet/chest_xray/train/PNEUMONIA/')
- X_train = np.append(norm_images, pneu_images, axis=0)
- y_train = np.append(norm_labels, pneu_labels)
在Jupyter Notebook中可以输入这两个语句查看是否成功导入了数据
同理,可以查看每一个类别分别有多少张图片
这个展示并不是必须的,只是这里可以用于检查图片的颜色通道、尺寸等是否已经转化为期待的样子了。
- fig, axes = plt.subplots(ncols=7, nrows=2, figsize=(16, 4))
-
- indices = np.random.choice(len(X_train), 14)
- counter = 0
-
- for 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 += 1
- plt.show()
和上方加载训练集的图片过程类似,函数也是相同的,测试集共有624张图片
- print('Loading test images')
- # Do the exact same thing as what we have done on train data
- norm_images_test, norm_labels_test = load_normal('/Users/liqun/Desktop/KS/MyPython/DataSet/chest_xray/test/NORMAL/')
- pneu_images_test, pneu_labels_test = load_pneumonia('/Users/liqun/Desktop/KS/MyPython/DataSet/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库,它可以存储这些转化好的图片,下次再使用的时候就不需要重新重复上面的操作了
- with open('pneumonia_data.pickle', 'wb') as f:
- pickle.dump((X_train, X_test, y_train, y_test), f)
-
- with open('pneumonia_data.pickle', 'rb') as f:
- (X_train, X_test, y_train, y_test) = pickle.load(f)
此时,两个Y变量都由以字符串数据类型编写的正常变量、细菌或病毒组成。但是,神经网络无法接受这样的标签。因此,我们需要将其转换为单热格式。
- y_train = y_train[:, np.newaxis]
- y_test = y_test[:, np.newaxis]
- one_hot_encoder = OneHotEncoder(sparse=False)
- y_train_one_hot = one_hot_encoder.fit_transform(y_train)
- y_test_one_hot = one_hot_encoder.transform(y_test)
- 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)
增加数据指的是,我们将通过在每个样本上创建更多具有某种随机性的样本来增加用于训练的数据数量。这些随机性可能包括翻译、旋转、缩放、剪切和翻转。这种技术能够帮助我们的神经网络分类器减少过度拟合。
- datagen = 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)
如有需要,可以了解一下ImageDataGenerator函数的参数构成:
我们需要确保第一层接受与图像大小完全相同的形状。值得注意的是,我们需要定义的只是(宽度、高度、通道),而不是(样本、宽度、高度、通道)。
之后,该输入层连接到几个卷积池层,最终被扁平并连接到Dense层。模型中的所有隐藏层都在使用Relu激活函数,RelU的计算速度快,因此所需的训练时间短。最后,连接的最后一层是输出,它由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)
在构建模型后,现在我们使用categorical_crossentropy损失函数和Adam优化器编译神经网络。使用这个点损失函数的原因是,它是多类分类任务中常用的函数。而Adam作为优化器,是大多数神经网络任务中最小化损失值的最佳优化器。
- model.compile(loss='categorical_crossentropy',
- optimizer='adam', metrics=['acc'])
一共迭代了30次,这里仅展示了最后四次的迭代结果
- 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()
由于我们前面将数据增强了,所以我们发现模型训练结果没有出现过拟合的状况,尽管accuracy的波动仍然存在,但整体的趋势是较为正常且能接受的。
- predictions = model.predict(X_test)
- predictions = one_hot_encoder.inverse_transform(predictions)
- cm = confusion_matrix(y_test, predictions)
-
- 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()
根据上面的混淆矩阵,我们可以看到22张病毒X射线图像被预测为细菌。这可能是因为这两种肺炎类型很难区分。但是,至少我们的模型能够很好地预测细菌引起的肺炎,因为242个样本中有233个被正确分类。
🫥 以上是本次深度学习实战的全部解析,以下是完整代码,需要请自取:
- import os
- import cv2
- import pickle # Used to save variables
- import numpy as np
- import matplotlib.pyplot as plt
- import seaborn as sns
- from tqdm import tqdm # Used to display progress bar
- from sklearn.preprocessing import OneHotEncoder
- from sklearn.metrics import confusion_matrix
- from keras.models import Model, load_model
- from keras.layers import Dense, Input, Conv2D, MaxPool2D, Flatten
- from keras.preprocessing.image import ImageDataGenerator # Used to generate images
-
- np.random.seed(22)
-
- # Do not forget to include the last slash
- def 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
- if image == '.DS_Store' : continue
- 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_labels
-
- def 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
- if image == '.DS_Store' : continue
- 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_labels
-
-
- print('Loading images')
- # All images are stored in _images, all labels are in _labels
- norm_images, norm_labels = load_normal('/Users/liqun/Desktop/KS/MyPython/DataSet/chest_xray/train/NORMAL/')
- pneu_images, pneu_labels = load_pneumonia('/Users/liqun/Desktop/KS/MyPython/DataSet/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_train
- y_train = np.append(norm_labels, pneu_labels)
-
- print(X_train.shape)
- print(y_train.shape)
- # Finding out the number of samples of each class
- print(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 = 0
-
- for 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 += 1
- # plt.show()
-
-
- print('Loading test images')
- # Do the exact same thing as what we have done on train data
- norm_images_test, norm_labels_test = load_normal('/Users/liqun/Desktop/KS/MyPython/DataSet/chest_xray/test/NORMAL/')
- pneu_images_test, pneu_labels_test = load_pneumonia('/Users/liqun/Desktop/KS/MyPython/DataSet/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 use
- with open('pneumonia_data.pickle', 'wb') as f:
- pickle.dump((X_train, X_test, y_train, y_test), f)
-
- # Here's how to load it
- with 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 data
- y_train = y_train[:, np.newaxis]
- y_test = y_test[:, np.newaxis]
-
- # Initialize OneHotEncoder object
- one_hot_encoder = OneHotEncoder(sparse=False)
-
- # Convert all labels to one-hot
- y_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 channel
- 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)
-
- print('Data augmentation')
- # Generate new images with some randomness
- datagen = 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 network
- input_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 epoch
- history = model.fit_generator(train_gen, epochs=30,
- validation_data=(X_test, y_test_one_hot))
-
- # Saving model
- model.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 data
- predictions = 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 matrix
- cm = 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()
🫥 本次实验所用到的数据集也为大家整理好了,从这里下载即可:
链接: https://pan.baidu.com/s/1h4Ve-YiXw0FyJDXCFlU1eA?pwd=qak4 提取码: qak4
———————————————————————————————————————————
码字不易
如果我的文章对你有帮助的话 麻烦留下赞哇 😋
感谢浏览!