一、写在前面
上两期我们基于TensorFlow和Pytorch环境做了图像识别的多分类任务建模。这一期我们做误判病例分析,分两节介绍,分别基于TensorFlow和Pytorch环境的建模和分析。
本期以健康组、肺结核组、COVID-19组、细菌性(病毒性)肺炎组为数据集,基于TensorFlow环境,构建mobilenet_v2多分类模型,因为它建模速度快。
同样,基于GPT-4辅助编程,这次改写过程会简单展示。
二、误判病例分析实战
使用胸片的数据集:肺结核病人和健康人的胸片的识别。其中,健康人900张,肺结核病人700张,COVID-19病人549张、细菌性(病毒性)肺炎组900张,分别存入单独的文件夹中。
直接分享代码:
- ######################################导入包###################################
- from tensorflow import keras
- import tensorflow as tf
- from tensorflow.python.keras.layers import Dense, Flatten, Conv2D, MaxPool2D, Dropout, Activation, Reshape, Softmax, GlobalAveragePooling2D, BatchNormalization
- from tensorflow.python.keras.layers.convolutional import Convolution2D, MaxPooling2D
- from tensorflow.python.keras import Sequential
- from tensorflow.python.keras import Model
- from tensorflow.python.keras.optimizers import adam_v2
- import numpy as np
- import matplotlib.pyplot as plt
- from tensorflow.python.keras.preprocessing.image import ImageDataGenerator, image_dataset_from_directory
- from tensorflow.python.keras.layers.preprocessing.image_preprocessing import RandomFlip, RandomRotation, RandomContrast, RandomZoom, RandomTranslation
- import os,PIL,pathlib
- import warnings
-
- #设置GPU
- gpus = tf.config.list_physical_devices("GPU")
-
- warnings.filterwarnings("ignore") #忽略警告信息
- plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
- plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
-
- ################################导入数据集#####################################
- data_dir = "./MTB-1"
- data_dir = pathlib.Path(data_dir)
- image_count = len(list(data_dir.glob('*/*')))
- print("图片总数为:", image_count)
-
- batch_size = 32
- img_height = 100
- img_width = 100
-
- # 创建一个数据集,其中包含所有图像的路径。
- list_ds = tf.data.Dataset.list_files(str(data_dir/'*/*'), shuffle=True)
- # 切分为训练集和验证集
- val_size = int(image_count * 0.2)
- train_ds = list_ds.skip(val_size)
- val_ds = list_ds.take(val_size)
-
- class_names = np.array(sorted([item.name for item in data_dir.glob('*') if item.name != "LICENSE.txt"]))
- print(class_names)
-
- def get_label(file_path):
- parts = tf.strings.split(file_path, os.path.sep)
- one_hot = parts[-2] == class_names
- return tf.argmax(one_hot)
-
- def decode_img(img):
- img = tf.image.decode_image(img, channels=3, expand_animations=False) # 指定 channels 参数
- img = tf.image.resize(img, [img_height, img_width])
- img = img / 255.0 # normalize to [0,1] range
- return img
-
-
- # 在创建数据集时,添加一个新的元素:数据集类型
- def process_path_with_filename_and_dataset_type(file_path, dataset_type):
- label = get_label(file_path)
- img = tf.io.read_file(file_path)
- img = decode_img(img)
- return img, label, file_path, dataset_type
-
- AUTOTUNE = tf.data.AUTOTUNE
-
- # 在此处对train_ds和val_ds进行图像处理,包括添加文件名信息和数据集类型信息
- train_ds_with_filenames_and_type = train_ds.map(lambda x: process_path_with_filename_and_dataset_type(x, 'Train'), num_parallel_calls=AUTOTUNE)
- val_ds_with_filenames_and_type = val_ds.map(lambda x: process_path_with_filename_and_dataset_type(x, 'Val'), num_parallel_calls=AUTOTUNE)
-
- # 合并训练集和验证集
- all_ds_with_filenames_and_type = train_ds_with_filenames_and_type.concatenate(val_ds_with_filenames_and_type)
-
- # 对训练数据集进行批处理和预加载
- train_ds_with_filenames_and_type = train_ds_with_filenames_and_type.batch(batch_size)
- train_ds_with_filenames_and_type = train_ds_with_filenames_and_type.prefetch(buffer_size=AUTOTUNE)
-
- # 对验证数据集进行批处理和预加载
- val_ds_with_filenames_and_type = val_ds_with_filenames_and_type.batch(batch_size)
- val_ds_with_filenames_and_type = val_ds_with_filenames_and_type.prefetch(buffer_size=AUTOTUNE)
-
- # 在进行模型训练时,不需要文件名和数据集类型信息,所以在此处移除
- train_ds = train_ds_with_filenames_and_type.map(lambda x, y, z, t: (x, y))
- val_ds = val_ds_with_filenames_and_type.map(lambda x, y, z, t: (x, y))
-
- for image, label, path, dataset_type in train_ds_with_filenames_and_type.take(1):
- print("Image shape: ", image.numpy().shape)
- print("Label: ", label.numpy())
- print("Path: ", path.numpy())
- print("Dataset type: ", dataset_type.numpy())
-
- train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)
- val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)
-
- plt.figure(figsize=(10, 8)) # 图形的宽为10高为5
- plt.suptitle("数据展示")
-
- for images, labels, paths, dataset_types in train_ds_with_filenames_and_type.take(1):
- for i in range(15):
- plt.subplot(4, 5, i + 1)
- plt.xticks([])
- plt.yticks([])
- plt.grid(False)
- plt.imshow(images[i].numpy())
- plt.xlabel(class_names[labels[i]])
- plt.show()
-
-
- ######################################数据增强函数################################
-
- data_augmentation = Sequential([
- RandomFlip("horizontal_and_vertical"),
- RandomRotation(0.2),
- RandomContrast(1.0),
- RandomZoom(0.5, 0.2),
- RandomTranslation(0.3, 0.5),
- ])
-
- def prepare(ds, augment=False):
- ds = ds.map(lambda x, y, z, t: (data_augmentation(x, training=True), y, z, t) if augment else (x, y, z, t),
- num_parallel_calls=AUTOTUNE)
- return ds
-
- # 注意这里变量名的更改
- train_ds_with_filenames_and_type = prepare(train_ds_with_filenames_and_type, augment=True)
-
- # 在进行模型训练时,不需要文件名和数据集类型信息,所以在此处移除
- train_ds = train_ds_with_filenames_and_type.map(lambda x, y, z, t: (x, y))
- val_ds = val_ds_with_filenames_and_type.map(lambda x, y, z, t: (x, y))
-
- train_ds = train_ds.prefetch(buffer_size=AUTOTUNE)
- val_ds = val_ds.prefetch(buffer_size=AUTOTUNE)
-
-
- ###############################导入mobilenet_v2################################
- #获取预训练模型对输入的预处理方法
- from tensorflow.python.keras.applications import mobilenet_v2
- from tensorflow.python.keras import Input, regularizers
- IMG_SIZE = (img_height, img_width, 3)
-
- base_model = mobilenet_v2.MobileNetV2(input_shape=IMG_SIZE,
- include_top=False, #是否包含顶层的全连接层
- weights='imagenet')
-
- inputs = Input(shape=IMG_SIZE)
- #模型
- x = base_model(inputs, training=False) #参数不变化
- #全局池化
- x = GlobalAveragePooling2D()(x)
- #BatchNormalization
- x = BatchNormalization()(x)
- #Dropout
- x = Dropout(0.8)(x)
- #Dense
- x = Dense(128, kernel_regularizer=regularizers.l2(0.1))(x) # 全连接层减少到128,添加 L2 正则化
- #BatchNormalization
- x = BatchNormalization()(x)
- #激活函数
- x = Activation('relu')(x)
- #输出层
- outputs = Dense(4, kernel_regularizer=regularizers.l2(0.1))(x) # 添加 L2 正则化,改变输出层的神经元数量为4
- #BatchNormalization
- outputs = BatchNormalization()(outputs)
- #激活函数
- outputs = Activation('softmax')(outputs) # 使用softmax激活函数,因为是多分类问题
- #整体封装
- model = Model(inputs, outputs)
- #打印模型结构
- print(model.summary())
- #############################编译模型#########################################
- #定义优化器
- from tensorflow.python.keras.optimizers import adam_v2, rmsprop_v2
-
- optimizer = adam_v2.Adam()
-
-
- #编译模型
- model.compile(optimizer=optimizer,
- loss='sparse_categorical_crossentropy', # 因为是多分类问题,所以损失函数选择sparse_categorical_crossentropy
- metrics=['accuracy'])
-
- #训练模型
- from tensorflow.python.keras.callbacks import ModelCheckpoint, Callback, EarlyStopping, ReduceLROnPlateau, LearningRateScheduler
-
- NO_EPOCHS = 50
- PATIENCE = 10
- VERBOSE = 1
-
- # 设置动态学习率
- annealer = LearningRateScheduler(lambda x: 1e-5 * 0.99 ** (x+NO_EPOCHS))
-
- # 设置早停
- earlystopper = EarlyStopping(monitor='loss', patience=PATIENCE, verbose=VERBOSE)
-
- #
- checkpointer = ModelCheckpoint('mtb_jet_best_model_mobilenetv3samll-1.h5',
- monitor='val_accuracy',
- verbose=VERBOSE,
- save_best_only=True,
- save_weights_only=True)
-
- train_model = model.fit(train_ds,
- epochs=NO_EPOCHS,
- verbose=1,
- validation_data=val_ds,
- callbacks=[earlystopper, checkpointer, annealer])
-
- #保存模型
- #model.save('mtb_jet_best_model_mobilenet-1.h5')
- #print("The trained model has been saved.")
-
-
- ###########################误判病例分析#################################
- import pandas as pd
-
- # 提取图片的信息并预测
- data_list = []
- for image, label, path, dataset_type in all_ds_with_filenames_and_type:
- # 获取图片名称、类别信息
- path_parts = path.numpy().decode('utf-8').split('/')
- dataset_type = dataset_type.numpy().decode('utf-8')
- true_class = class_names[label.numpy()]
- image_name = path_parts[-1]
-
- # 使用模型预测图片的类别
- img_array = np.expand_dims(image, axis=0)
- predictions = model.predict(img_array)
- pred_class = class_names[np.argmax(predictions)]
-
- # 根据预测结果判断所属的组别
- if true_class == pred_class:
- group = 'A'
- elif true_class == 'COVID-19':
- if pred_class == 'Normal':
- group = 'B'
- elif pred_class == 'Pneumonia':
- group = 'C'
- elif pred_class == 'Tuberculosis':
- group = 'D'
- elif true_class == 'Normal':
- if pred_class == 'COVID-19':
- group = 'E'
- elif pred_class == 'Pneumonia':
- group = 'F'
- elif pred_class == 'Tuberculosis':
- group = 'G'
- elif true_class == 'Pneumonia':
- if pred_class == 'COVID-19':
- group = 'H'
- elif pred_class == 'Normal':
- group = 'I'
- elif pred_class == 'Tuberculosis':
- group = 'J'
- elif true_class == 'Tuberculosis':
- if pred_class == 'COVID-19':
- group = 'H'
- elif pred_class == 'Normal':
- group = 'I'
- elif pred_class == 'Pneumonia':
- group = 'J'
-
- # 保存图片的信息和预测结果
- data_list.append([image_name, dataset_type, pred_class, group])
-
- # 将结果转化为DataFrame并保存为csv文件
- result = pd.DataFrame(data_list, columns=["原始图片的名称", "属于训练集还是验证集", "预测为分组类型", "判定的组别"])
- result.to_csv("result-m-t.csv", index=False)
三、改写过程
先说策略:首先,先把二分类的误判病例分析代码改成四分类的;其次,用咒语让GPT-4帮我们续写代码已达到误判病例分析。
策略的理由:之前介绍过,做误判病例分析是需要读取图片的路径信息。悲剧的是,我们之前在读取数据的时候使用的是“image_dataset_from_directory”函数,它不提供路径信息。因此,在二分类的误判病例分析的教程中,我们修改了数据读取的代码,因此,在此基础上进行修改,效率最高!
提供咒语如下:
①改写{代码1},改变成4分类的建模。代码1为:{XXX};
②在{代码1}的基础上改写代码,达到下面要求:
(1)首先,提取出所有图片的“原始图片的名称”、“属于训练集还是验证集”、“预测为分组类型”;文件的路劲格式为:例如,“MTB-1\Normal\XXX.png”属于Normal,“MTB-1\COVID-19\XXX.jpg”属于COVID-19,“MTB-1\Pneumonia\XXX.jpeg”属于Pneumonia,“MTB-1\Tuberculosis\XXX.png”属于Tuberculosis;
(2)其次,根据样本预测结果,把样本分为以下若干组:(a)预测正确的图片,全部判定为A组;(b)本来就是COVID-19的图片,预测为Normal,判定为B组;(c)本来就是COVID-19的图片,预测为Pneumonia,判定为C组;(d)本来就是COVID-19的图片,预测为Tuberculosis,判定为D组;(e)本来就是Normal的图片,预测为COVID-19,判定为E组;(f)本来就是Normal的图片,预测为Pneumonia,判定为F组;(g)本来就是Normal的图片,预测为Tuberculosis,判定为G组;(h)本来就是Pneumonia的图片,预测为COVID-19,判定为H组;(i)本来就是Pneumonia的图片,预测为Normal,判定为I组;(j)本来就是Pneumonia的图片,预测为Tuberculosis,判定为J组;(k)本来就是Tuberculosis的图片,预测为COVID-19,判定为H组;(l)本来就是Tuberculosis的图片,预测为Normal,判定为I组;(m)本来就是Tuberculosis的图片,预测为Pneumonia,判定为J组;
(3)居于以上计算的结果,生成一个名为result-m.csv表格文件。列名分别为:“原始图片的名称”、“属于训练集还是验证集”、“预测为分组类型”、“判定的组别”。其中,“原始图片的名称”为所有图片的图片名称;“属于训练集还是验证集”为这个图片属于训练集还是验证集;“预测为分组类型”为模型预测该样本是哪一个分组;“判定的组别”为根据步骤(2)判定的组别,从A到J一共十组选择一个。
(4)需要把所有的图片都进行上面操作,注意是所有图片,而不只是一个批次的图片。
代码1为:{XXX}
③还需要根据报错做一些调整即可,自行调整。
最后,看看结果:

四、数据
链接:https://pan.baidu.com/s/1rqu15KAUxjNBaWYfEmPwgQ?pwd=xfyn
提取码:xfyn