diff --git a/choose_thre_area.py b/choose_thre_area.py index 63507be..111d449 100644 --- a/choose_thre_area.py +++ b/choose_thre_area.py @@ -8,7 +8,6 @@ import codecs import json import matplotlib.pyplot as plt -plt.switch_backend('agg') from solver import Solver from models.model import Model from datasets.steel_dataset import provider @@ -20,9 +19,9 @@ class ChooseThresholdMinArea(): ''' 选择每一类的像素阈值和最小连通域 ''' + def __init__(self, model, model_name, valid_loader, fold, save_path, class_num=4): ''' 模型初始化 - Args: model: 使用的模型 model_name: 当前模型的名称 @@ -43,7 +42,7 @@ def __init__(self, model, model_name, valid_loader, fold, save_path, class_num=4 def choose_threshold_minarea(self): ''' 采用网格法搜索各个类别最优像素阈值和最优最小连通域,并画出各个类别搜索过程中的热力图 - + Return: best_thresholds_little: 每一个类别的最优阈值 best_minareas_little: 每一个类别的最优最小连通取余 @@ -52,28 +51,36 @@ def choose_threshold_minarea(self): init_thresholds_range, init_minarea_range = np.arange(0.50, 0.71, 0.03), np.arange(768, 2305, 256) # 阈值列表和最小连通域列表,大小为 Nx4 - thresholds_table_big = np.array([init_thresholds_range, init_thresholds_range, \ + thresholds_table_big = np.array([init_thresholds_range, init_thresholds_range, init_thresholds_range, init_thresholds_range]) # 阈值列表 - minareas_table_big = np.array([init_minarea_range, init_minarea_range, \ + minareas_table_big = np.array([init_minarea_range, init_minarea_range, init_minarea_range, init_minarea_range]) # 最小连通域列表 f, axes = plt.subplots(figsize=(28.8, 18.4), nrows=2, ncols=self.class_num) cmap = sns.cubehelix_palette(start=1.5, rot=3, gamma=0.8, as_cmap=True) - best_thresholds_big, best_minareas_big, max_dices_big = self.grid_search(thresholds_table_big, minareas_table_big, axes[0,:], cmap) - print('best_thresholds_big:{}, best_minareas_big:{}, max_dices_big:{}'.format(best_thresholds_big, best_minareas_big, max_dices_big)) + best_thresholds_big, best_minareas_big, max_dices_big = self.grid_search(thresholds_table_big, + minareas_table_big, axes[0, :], cmap) + print('best_thresholds_big:{}, best_minareas_big:{}, max_dices_big:{}'.format(best_thresholds_big, + best_minareas_big, max_dices_big)) # 开始细分类 thresholds_table_little, minareas_table_little = list(), list() for best_threshold_big, best_minarea_big in zip(best_thresholds_big, best_minareas_big): - thresholds_table_little.append(np.arange(best_threshold_big-0.03, best_threshold_big+0.03, 0.015)) # 阈值列表 - minareas_table_little.append(np.arange(best_minarea_big-256, best_minarea_big+257, 128)) # 像素阈值列表 - thresholds_table_little, minareas_table_little = np.array(thresholds_table_little), np.array(minareas_table_little) - - best_thresholds_little, best_minareas_little, max_dices_little = self.grid_search(thresholds_table_little, minareas_table_little, axes[1,:], cmap) - print('best_thresholds_little:{}, best_minareas_little:{}, max_dices_little:{}'.format(best_thresholds_little, best_minareas_little, max_dices_little)) - - f.savefig(os.path.join(self.save_path, self.model_name + '_fold'+str(self.fold)), bbox_inches='tight') + thresholds_table_little.append( + np.arange(best_threshold_big - 0.03, best_threshold_big + 0.03, 0.015)) # 阈值列表 + minareas_table_little.append(np.arange(best_minarea_big - 256, best_minarea_big + 257, 128)) # 像素阈值列表 + thresholds_table_little, minareas_table_little = np.array(thresholds_table_little), np.array( + minareas_table_little) + + best_thresholds_little, best_minareas_little, max_dices_little = self.grid_search(thresholds_table_little, + minareas_table_little, + axes[1, :], cmap) + print('best_thresholds_little:{}, best_minareas_little:{}, max_dices_little:{}'.format(best_thresholds_little, + best_minareas_little, + max_dices_little)) + + f.savefig(os.path.join(self.save_path, self.model_name + '_fold' + str(self.fold)), bbox_inches='tight') # plt.show() plt.close() @@ -82,13 +89,11 @@ def choose_threshold_minarea(self): def grid_search(self, thresholds_table, minareas_table, axes, cmap): ''' 给定包含各个类别搜索区间的thresholds_table和minareas_table,求的各个类别的最优像素阈值,最优最小连通域,最高dice; 并画出各个类别搜索过程中的热力图 - Args: thresholds_table: 待搜索的阈值范围,维度为[4, N],numpy类型 minareas_table: 待搜索的最小连通域范围,维度为[4, N],numpy类型 axes: 画各个类别搜索热力图时所需要的画柄,尺寸为[class_num] cmap: 画图时所需要的cmap - return: best_thresholds: 各个类别的最优像素阈值,尺寸为[class_num] best_minareas: 各个类别的最优最小连通域,尺寸为[class_num] @@ -97,15 +102,12 @@ def grid_search(self, thresholds_table, minareas_table, axes, cmap): dices_table = np.zeros((self.class_num, np.shape(thresholds_table)[1], np.shape(minareas_table)[1])) tbar = tqdm.tqdm(self.valid_loader) with torch.no_grad(): - for i, samples in enumerate(tbar): - if len(samples) == 0: - continue - images, masks = samples[0], samples[1] + for i, (images, masks) in enumerate(tbar): # 完成网络的前向传播 masks_predict_allclasses = self.solver.forward(images) dices_table += self.grid_search_batch(thresholds_table, minareas_table, masks_predict_allclasses, masks) - dices_table = dices_table/len(tbar) + dices_table = dices_table / len(tbar) best_thresholds, best_minareas, max_dices = list(), list(), list() # 处理每一类的预测结果 for each_class, dices_oneclass_table in enumerate(dices_table): @@ -116,21 +118,21 @@ def grid_search(self, thresholds_table, minareas_table, axes, cmap): best_minareas.append(minareas_table[each_class, max_location[1]]) max_dices.append(max_dice) - data = pd.DataFrame(data=dices_oneclass_table, index=np.around(thresholds_table[each_class,:], 3), columns=minareas_table[each_class,:]) - sns.heatmap(data, linewidths=0.05, ax=axes[each_class], vmax=np.max(dices_oneclass_table), vmin=np.min(dices_oneclass_table), cmap=cmap, + data = pd.DataFrame(data=dices_oneclass_table, index=np.around(thresholds_table[each_class, :], 3), + columns=minareas_table[each_class, :]) + sns.heatmap(data, linewidths=0.05, ax=axes[each_class], vmax=np.max(dices_oneclass_table), + vmin=np.min(dices_oneclass_table), cmap=cmap, annot=True, fmt='.4f') axes[each_class].set_title('search result') return best_thresholds, best_minareas, max_dices def grid_search_batch(self, thresholds_table, minareas_table, masks_predict_allclasses, masks_allclasses): '''给定thresholds、minareas矩阵、一个batch的预测结果和真实标签,遍历每个类的每一个组合得到对应的dice值 - Args: thresholds_table: 待搜索的阈值范围,维度为[4, N],numpy类型 minareas_table: 待搜索的最小连通域范围,维度为[4, N],numpy类型 masks_predict_allclasses: 所有类别的某个batch的预测结果且未经过sigmoid,维度为[batch_size, class_num, height, width] masks_allclasses: 所有类别的某个batch的真实类标,维度为[batch_size, class_num, height, width] - Return: dices_table: 各个类别在其各自的所有搜索组合中所得到的dice值,维度为[4, M, N] ''' @@ -149,13 +151,13 @@ def grid_search_batch(self, thresholds_table, minareas_table, masks_predict_allc def post_process(self, thresholds_range, minareas_range, masks_predict_oneclass, masks_oneclasses): '''给定某个类别的某个batch的数据,遍历所有搜索组合,得到每个组合的dice值 - + Args: thresholds_range: 具体某个类别的像素阈值搜索区间,尺寸为[M] minareas_range: 具体某个类别的最小连通域搜索区间,尺寸为[N] masks_predict_oneclass: 预测出的某个类别的该batch的tensor向量且未经过sigmoid,维度为[batch_size, height, width] masks_oneclasses: 某个类别的该batch的真实类标,维度为[batch_size, height, width] - + Return: dices_range: 某个类别的该batch的所有搜索组合得到dice矩阵,维度为[M, N] ''' @@ -188,11 +190,10 @@ def post_process(self, thresholds_range, minareas_range, masks_predict_oneclass, def get_model(model_name, load_path): ''' 加载网络模型并加载对应的权重 - - Args: + Args: model_name: 当前模型的名称 load_path: 当前模型的权重路径 - + Return: model: 加载出来的模型 ''' @@ -203,16 +204,15 @@ def get_model(model_name, load_path): if __name__ == "__main__": config = get_seg_config() - mean=(0.485, 0.456, 0.406) - std=(0.229, 0.224, 0.225) + mean = (0.485, 0.456, 0.406) + std = (0.229, 0.224, 0.225) mask_only = True - dataloaders = provider(config.dataset_root, os.path.join(config.dataset_root, 'train.csv'), mean, std, config.batch_size, config.num_workers, config.n_splits, mask_only) + dataloaders = provider(config.dataset_root, os.path.join(config.dataset_root, 'train.csv'), mean, std, + config.batch_size, config.num_workers, config.n_splits, mask_only) results = {} # 存放权重的路径 model_path = os.path.join(config.save_path, config.model_name) - best_thresholds_sum, best_minareas_sum, max_dices_sum = [0 for x in range(len(dataloaders))], \ - [0 for x in range(len(dataloaders))], [0 for x in range(len(dataloaders))] for fold_index, [train_loader, valid_loader] in enumerate(dataloaders): if fold_index != 1: continue @@ -221,18 +221,13 @@ def get_model(model_name, load_path): load_path = os.path.join(model_path, '%s_fold%d_best.pth' % (config.model_name, fold_index)) # 加载模型 model = get_model(config.model_name, load_path) - mychoose_threshold_minarea = ChooseThresholdMinArea(model, config.model_name, valid_loader, fold_index, model_path) + mychoose_threshold_minarea = ChooseThresholdMinArea(model, config.model_name, valid_loader, fold_index, + model_path) best_thresholds, best_minareas, max_dices = mychoose_threshold_minarea.choose_threshold_minarea() result = {'best_thresholds': best_thresholds, 'best_minareas': best_minareas, 'max_dices': max_dices} - results[str(fold_index)] = result - best_thresholds_sum = [x+y for x,y in zip(best_thresholds_sum, best_thresholds)] - best_minareas_sum = [x+y for x,y in zip(best_minareas_sum, best_minareas)] - max_dices_sum = [x+y for x,y in zip(max_dices_sum, max_dices)] - best_thresholds_average, best_minareas_average, max_dices_average = [x/len(dataloaders) for x in best_thresholds_sum], \ - [x/len(dataloaders) for x in best_minareas_sum], [x/len(dataloaders) for x in max_dices_sum] - results['mean'] = {'best_thresholds': best_thresholds_average, 'best_minareas': best_minareas_average, 'max_dices': max_dices_average} + results[str(fold_index)] = result with codecs.open(model_path + '/result.json', 'w', "utf-8") as json_file: json.dump(results, json_file, ensure_ascii=False) - print('save the result') + print('save the result') \ No newline at end of file diff --git a/create_submission.py b/create_submission.py index bc05e31..d61c78f 100644 --- a/create_submission.py +++ b/create_submission.py @@ -12,7 +12,7 @@ os.system('pip install /kaggle/input/segmentation_models/pretrainedmodels-0.7.4/ > /dev/null') os.system('pip install /kaggle/input/segmentation_models/EfficientNet-PyTorch/ > /dev/null') os.system('pip install /kaggle/input/segmentation_models/segmentation_models.pytorch/ > /dev/null') - package_path = '/kaggle/input/sources' # add unet script dataset + package_path = '/kaggle/input/sources' # add unet script dataset import sys sys.path.append(package_path) from classify_segment import Classify_Segment_Folds, Classify_Segment_Fold, Classify_Segment_Folds_Split @@ -82,17 +82,20 @@ def create_submission(classify_splits, seg_splits, model_name, batch_size, num_w num_workers=num_workers, pin_memory=True ) - # if len(classify_splits) == 1 and len(seg_splits) == 1: - # classify_segment = Classify_Segment_Fold(model_name, classify_splits[0], model_path, tta_flag=tta_flag).classify_segment - # elif len(classify_splits) == len(seg_splits): - # classify_segment = Classify_Segment_Folds(model_name, classify_splits, model_path, tta_flag=tta_flag).classify_segment_folds - # elif len(classify_splits) != len(seg_splits): - classify_segment = Classify_Segment_Folds_Split(model_name, classify_splits, seg_splits, model_path, tta_flag=tta_flag).classify_segment_folds + if len(classify_splits) == 1 and len(seg_splits) == 1: + classify_segment = Classify_Segment_Fold(model_name, classify_splits[0], model_path, tta_flag=tta_flag).classify_segment + elif len(classify_splits) == len(seg_splits): + classify_segment = Classify_Segment_Folds(model_name, classify_splits, model_path, tta_flag=tta_flag).classify_segment_folds + elif len(classify_splits) != len(seg_splits): + classify_segment = Classify_Segment_Folds_Split(model_name, classify_splits, seg_splits, model_path, tta_flag=tta_flag).classify_segment_folds # start prediction predictions = [] for i, (fnames, images) in enumerate(tqdm(test_loader)): - results = classify_segment(images, average_strategy=average_strategy).detach().cpu().numpy() + if len(classify_splits) != len(seg_splits): + results = classify_segment(images, average_strategy=average_strategy).detach().cpu().numpy() + else: + results = classify_segment(images).detach().cpu().numpy() for fname, preds in zip(fnames, results): for cls, pred in enumerate(preds): @@ -107,13 +110,13 @@ def create_submission(classify_splits, seg_splits, model_name, batch_size, num_w if __name__ == "__main__": # 设置超参数 - model_name = 'unet_resnet34' + model_name = 'unet_efficientnet_b4' num_workers = 12 - batch_size = 4 + batch_size = 1 mean = (0.485, 0.456, 0.406) std = (0.229, 0.224, 0.225) - classify_splits = [1] # [0, 1, 2, 3, 4] - segment_splits = [0, 1, 2, 3, 4] + classify_splits = [1]# [0, 1, 2, 3, 4] + segment_splits = [1] tta_flag = True average_strategy = False diff --git a/datasets/steel_dataset.py b/datasets/steel_dataset.py index ba83ac5..08c13a7 100644 --- a/datasets/steel_dataset.py +++ b/datasets/steel_dataset.py @@ -318,15 +318,15 @@ def classify_provider( if __name__ == "__main__": - data_folder = "./Steel_data" - df_path = "./Steel_data/train.csv" + data_folder = "datasets/Steel_data" + df_path = "datasets/Steel_data/train.csv" mean = (0.485, 0.456, 0.406) std = (0.229, 0.224, 0.225) batch_size = 12 num_workers = 4 n_splits = 1 - mask_only = False - crop = True + mask_only = True + crop = False height = 256 width = 512 # 测试分割数据集 diff --git a/train_classify.py b/train_classify.py index 2196e94..bafd184 100755 --- a/train_classify.py +++ b/train_classify.py @@ -67,7 +67,6 @@ def train(self, train_loader, valid_loader): optimizer = optim.Adam(self.model.module.parameters(), self.lr, weight_decay=self.weight_decay) lr_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, self.epoch+10) global_step = 0 - es = EarlyStopping(mode='min', patience=10) for epoch in range(self.epoch): epoch += 1 @@ -98,10 +97,6 @@ def train(self, train_loader, valid_loader): average_loss = epoch_loss / len(tbar) print('Finish Epoch [%d/%d], Average Loss: %.7f' % (epoch, self.epoch, average_loss)) - # 提前终止 - if es.step(average_loss): - break - # 验证模型 class_neg_accuracy, class_pos_accuracy, class_accuracy, neg_accuracy, pos_accuracy, accuracy, loss_valid = \ self.validation(valid_loader) @@ -160,8 +155,8 @@ def validation(self, valid_loader): if __name__ == "__main__": config = get_classify_config() - mean=(0.485, 0.456, 0.406) - std=(0.229, 0.224, 0.225) + mean = (0.485, 0.456, 0.406) + std = (0.229, 0.224, 0.225) dataloaders = classify_provider( config.dataset_root, os.path.join(config.dataset_root, 'train.csv'), diff --git a/train_segment.py b/train_segment.py index bf2de4d..2c60091 100644 --- a/train_segment.py +++ b/train_segment.py @@ -7,15 +7,14 @@ import codecs, json import time import pickle -import numpy as np +import random from models.model import Model -from utils.cal_dice_iou import Meter, compute_dice_class +from utils.cal_dice_iou import Meter from datasets.steel_dataset import provider from utils.set_seed import seed_torch from config import get_seg_config from solver import Solver from utils.loss import MultiClassesSoftBCEDiceLoss -from utils.easy_stopping import EarlyStopping class TrainVal(): @@ -50,7 +49,6 @@ def __init__(self, config, fold): # 加载损失函数 self.criterion = torch.nn.BCEWithLogitsLoss() - # self.criterion = MultiClassesSoftBCEDiceLoss(classes_num=self.class_num, size_average=True, weight=[1.0, 1.0]) # 保存json文件和初始化tensorboard TIMESTAMP = "{0:%Y-%m-%dT%H-%M-%S-%d}".format(datetime.datetime.now(), fold) @@ -77,8 +75,6 @@ def train(self, train_loader, valid_loader): lr_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, self.epoch+10) global_step = 0 - es = EarlyStopping(mode='min', patience=10) - for epoch in range(self.epoch): epoch += 1 epoch_loss = 0 @@ -112,28 +108,24 @@ def train(self, train_loader, valid_loader): average_loss = epoch_loss/len(tbar) print('Finish Epoch [%d/%d], Average Loss: %.7f' % (epoch, self.epoch, average_loss)) - # 提前终止 - if es.step(average_loss): - break - # 验证模型 - loss_valid, dice_valid, dice_classes = self.validation(valid_loader) - if dice_valid > self.max_dice_valid: + loss_valid, dice_valid, iou_valid = self.validation(valid_loader) + if dice_valid > self.max_dice_valid: is_best = True self.max_dice_valid = dice_valid - else: is_best = False - + else: + is_best = False + state = { 'epoch': epoch, 'state_dict': self.model.module.state_dict(), 'max_dice_valid': self.max_dice_valid, } - self.solver.save_checkpoint(os.path.join(self.model_path, '%s_fold%d.pth' % (self.model_name, self.fold)), state, is_best) + self.solver.save_checkpoint(os.path.join(self.model_path, '%s_fold%d.pth' % (self.model_name, self.fold)), + state, is_best) self.writer.add_scalar('valid_loss', loss_valid, epoch) self.writer.add_scalar('valid_dice', dice_valid, epoch) - for each_class, dice_class in enumerate(dice_classes): - self.writer.add_scalar('valid_dice_class{}'.format(each_class+1), dice_class, epoch) def validation(self, valid_loader): ''' 完成模型的验证过程 @@ -146,10 +138,10 @@ def validation(self, valid_loader): :return dice_classes: 验证集上各类的dice值 ''' self.model.eval() + meter = Meter() tbar = tqdm.tqdm(valid_loader) - loss_sum, dice_sum = 0, 0 + loss_sum = 0 - sum_classes = [0 for i in range(self.class_num)] with torch.no_grad(): for i, samples in enumerate(tbar): if len(samples) == 0: @@ -159,24 +151,20 @@ def validation(self, valid_loader): masks_predict = self.solver.forward(images) loss = self.solver.cal_loss(masks, masks_predict, self.criterion) loss_sum += loss.item() - + # 注意,损失函数中包含sigmoid函数,meter.update中也包含了sigmoid函数 - masks_predict_binary = torch.sigmoid(masks_predict) > 0.5 - for each_class in range(self.class_num): - masks_predict_oneclass = masks_predict_binary[:, each_class, ...] - masks_oneclasses = masks[:, each_class, ...] - dice = compute_dice_class(masks_predict_oneclass.float(), masks_oneclasses) - sum_classes[each_class] += dice - dice_sum += dice + # masks_predict_binary = torch.sigmoid(masks_predict) > 0.5 + meter.update(masks, masks_predict.detach().cpu()) + descript = "Val Loss: {:.7f}".format(loss.item()) tbar.set_description(desc=descript) - loss_mean = loss_sum/len(tbar) - dice_mean = dice_sum/len(tbar)/self.class_num - dice_classes = [x/len(tbar) for x in sum_classes] - print("loss_mean: %0.4f, dice_mean: %0.4f, dice_first_class: %0.4f, dice_second_class: %0.4f, dice_third_class: %0.4f, dice_forth_class: %0.4f" \ - % (loss_mean, dice_mean, dice_classes[0], dice_classes[1], dice_classes[2], dice_classes[3])) - return loss_mean, dice_mean, dice_classes - + loss_mean = loss_sum / len(tbar) + + dices, iou = meter.get_metrics() + dice, dice_neg, dice_pos = dices + print("IoU: %0.4f | dice: %0.4f | dice_neg: %0.4f | dice_pos: %0.4f" % (iou, dice, dice_neg, dice_pos)) + return loss_mean, dice, iou + def load_weight(self, weight_path): """加载权重 """ diff --git a/utils/cal_dice_iou.py b/utils/cal_dice_iou.py index 59e1a34..1d09f54 100644 --- a/utils/cal_dice_iou.py +++ b/utils/cal_dice_iou.py @@ -10,43 +10,33 @@ def predict(X, threshold): def metric(probability, truth, threshold=0.5, reduction='none'): - """Calculates dice of positive and negative images seperately - - probability and truth must be torch tensors, 维度为[batch, class_num, height, width] - """ - + '''Calculates dice of positive and negative images seperately''' + '''probability and truth must be torch tensors''' batch_size = len(truth) with torch.no_grad(): probability = probability.view(batch_size, -1) truth = truth.view(batch_size, -1) - assert(probability.shape == truth.shape) + assert (probability.shape == truth.shape) - # 将两者经过阈值变为二值数据 p = (probability > threshold).float() t = (truth > 0.5).float() t_sum = t.sum(-1) p_sum = p.sum(-1) - # torch.nonzero:返回输入二维张量中非零元素的索引,类型为张量,输出张量中的每行为两个元素,代表输入二维张量中非零元素的行列索引 - # 如果某个样本对应的真实掩模全部元素均为零,则该样本为负样本;否则的话则为正样本。 neg_index = torch.nonzero(t_sum == 0) pos_index = torch.nonzero(t_sum >= 1) dice_neg = (p_sum == 0).float() - dice_pos = 2 * (p*t).sum(-1)/((p+t).sum(-1)) + dice_pos = 2 * (p * t).sum(-1) / ((p + t).sum(-1)) - # 当预测为neg,且真实为neg的时候,计算dice_neg;当预测为pos,且真实为pos的时候,计算dice_pos - # 对于其余情况,dice均为零,所以可以不考虑 dice_neg = dice_neg[neg_index] dice_pos = dice_pos[pos_index] dice = torch.cat([dice_pos, dice_neg]) - # 求一个batch内的平均dice_neg, dice_pos, dice;并且使用零填充nan值 dice_neg = np.nan_to_num(dice_neg.mean().item(), 0) dice_pos = np.nan_to_num(dice_pos.mean().item(), 0) dice = dice.mean().item() - # 该batch中,真实有多少个负样本和正样本 num_neg = len(neg_index) num_pos = len(pos_index) @@ -55,26 +45,20 @@ def metric(probability, truth, threshold=0.5, reduction='none'): class Meter: '''A meter to keep track of iou and dice scores throughout an epoch''' + def __init__(self): - self.base_threshold = 0.5 # <<<<<<<<<<< here's the threshold + self.base_threshold = 0.5 # <<<<<<<<<<< here's the threshold self.base_dice_scores = [] self.dice_neg_scores = [] self.dice_pos_scores = [] self.iou_scores = [] def update(self, targets, outputs): - """ - :param targets: 真实掩模,维度为[batch, class_num, height, width] - :param outputs: 预测出的掩模,维度为[batch, class_num, height, width] - :return: None - """ probs = torch.sigmoid(outputs) - # 计算一系列的指标 dice, dice_neg, dice_pos, _, _ = metric(probs, targets, self.base_threshold) self.base_dice_scores.append(dice) self.dice_pos_scores.append(dice_pos) self.dice_neg_scores.append(dice_neg) - # 经过阈值后,计算IOU值 preds = predict(probs, self.base_threshold) iou = compute_iou_batch(preds, targets, classes=[1]) self.iou_scores.append(iou) @@ -84,7 +68,6 @@ def get_metrics(self): dice_neg = np.mean(self.dice_neg_scores) dice_pos = np.mean(self.dice_pos_scores) dices = [dice, dice_neg, dice_pos] - # np.nanmean:Compute the arithmetic mean along the specified axis, ignoring NaNs. iou = np.nanmean(self.iou_scores) return dices, iou @@ -93,18 +76,13 @@ def epoch_log(epoch, epoch_loss, meter, start): '''logging the metrics at the end of an epoch''' dices, iou = meter.get_metrics() dice, dice_neg, dice_pos = dices - print("Loss: %0.4f | IoU: %0.4f | dice: %0.4f | dice_neg: %0.4f | dice_pos: %0.4f" % (epoch_loss, iou, dice, dice_neg, dice_pos)) + print("Loss: %0.4f | IoU: %0.4f | dice: %0.4f | dice_neg: %0.4f | dice_pos: %0.4f" % ( + epoch_loss, iou, dice, dice_neg, dice_pos)) return dice, iou def compute_ious(pred, label, classes, ignore_index=255, only_present=True): - """computes iou for one ground truth mask and predicted mask - :param pred: 预测出的掩模,维度为[class_num, height, width],二值化数据 - :param label: 真实掩模,维度为[class_num, height, width],二值化数据 - 注意使用该函数计算IOU的时候,pred要在外部经过阈值二值化 - - 该函数并没有考虑真实和预测均为负样本的情况 - """ + '''computes iou for one ground truth mask and predicted mask''' pred[label == ignore_index] = 0 ious = [] for c in classes: @@ -115,7 +93,6 @@ def compute_ious(pred, label, classes, ignore_index=255, only_present=True): pred_c = pred == c intersection = np.logical_and(pred_c, label_c).sum() union = np.logical_or(pred_c, label_c).sum() - # 上面的 continue部分 if union != 0: ious.append(intersection / union) return ious if ious else [1] @@ -124,11 +101,9 @@ def compute_ious(pred, label, classes, ignore_index=255, only_present=True): def compute_iou_batch(outputs, labels, classes=None): '''computes mean iou for a batch of ground truth masks and predicted masks''' ious = [] - preds = np.copy(outputs) # copy is imp - labels = np.array(labels) # tensor to np - # 对于每一个样本的真实掩模以及预测掩模 + preds = np.copy(outputs) # copy is imp + labels = np.array(labels) # tensor to np for pred, label in zip(preds, labels): - # np.nanmean:Compute the arithmetic mean along the specified axis, ignoring NaNs. ious.append(np.nanmean(compute_ious(pred, label, classes))) iou = np.nanmean(ious) return iou @@ -136,12 +111,11 @@ def compute_iou_batch(outputs, labels, classes=None): def compute_dice_class(preds, targs): ''' 计算某一类的dice值,注意 preds 要在外部经过sigmoid函数 - Args: preds: 维度为[batch_size, 1, height, width],每一个值表示预测出的属于该类的概率 targs: 维度为[batch_size, 1, height, width],每一个值表示真实是否属于该类 - - Return: + + Return: 计算出的dice值 ''' n = preds.shape[0] # batch size为多少 @@ -156,12 +130,11 @@ def compute_dice_class(preds, targs): union = (preds + targs).sum(-1).float() ''' 输入图片真实类标与预测类标无并集有两种情况:第一种为预测与真实均没有类标,此时并集之和为0;第二种为真实有类标,但是预测完全错误,此时并集之和不为0; - 寻找输入图片真实类标与预测类标并集之和为0的情况,将其交集置为1,并集置为2,最后还有一个2*交集/并集,值为1; 其余情况,直接按照2*交集/并集计算,因为上面的并集并没有减去交集,所以需要拿2*交集,其最大值为1 ''' u0 = union == 0 intersect[u0] = 1 union[u0] = 2 - - return (2. * intersect / union).mean() \ No newline at end of file + + return (2. * intersect / union).mean() diff --git a/utils/data_augmentation.py b/utils/data_augmentation.py index 475d316..f600d8c 100644 --- a/utils/data_augmentation.py +++ b/utils/data_augmentation.py @@ -110,7 +110,7 @@ def data_augmentation(original_image, original_mask, crop=False, height=None, wi image_id, mask = make_mask(index, df) image_path = os.path.join(data_folder, 'train_images', image_id) image = cv2.imread(image_path) - image_aug, mask_aug = data_augmentation(image, mask, crop=True, height=256, width=400) + image_aug, mask_aug = data_augmentation(image, mask, crop=False, height=256, width=400) normalize = Normalize(mean=mean, std=std) image = normalize(image=image)['image'] image_aug = normalize(image=image_aug)['image']