一、项目背景介绍

这个数据集来源于Kaggle新赛 | HuBMAP: 识别人体肾脏组织图像中的肾小球~总奖金 6 万美金

kaggle比赛链接~~

我们最好的估计显示,地球上有超过70亿人,银河系中有3000亿颗恒星。相比之下,成年人体内含有37万亿个细胞。确定这些细胞之间的功能和关系是一项艰巨的任务。如果我们更好地了解细胞活动,人类健康的许多领域都会受到影响。这么多数据的问题与Kaggle社区非常匹配

正如人类基因组计划绘制了整个人类DNA一样,人类生物分子图谱计划(HuBMAP)是一项重大努力。由美国国立卫生研究院(NIH)赞助,HuBMAP正在努力促进开发一个框架,该框架在历史上首次在肾小球功能组织单位水平上绘制人体图谱。HuBMAP希望成为世界上最大的合作生物项目之一,旨在成为细胞水平上人体的开放地图。

这个竞赛,“黑客肾脏”,首先以单细胞分辨率绘制人类肾脏。
您面临的挑战是检测不同组织制备管道中的功能性组织单位 (FTU)。FTU 被定义为“以毛细管为中心的三维细胞块,使得该块中的每个细胞都与同一块中的任何其他细胞在扩散距离内”(de Bono,2013)。本次竞赛的目标是实施成功且强大的肾小球FTU检测器。

您还将有机会将您的发现提交给评委小组以供进一步考虑。成功的提交将构建确定细胞之间的关系如何影响个体健康所需的工具,资源和细胞图谱。
HuBMAP的进步将加速世界对细胞和组织与功能与人类健康之间关系的理解。这些数据集和见解可以被细胞和组织解剖学的研究人员、制药公司用来开发治疗方法,甚至可以被父母用来向他们的孩子展示人体的大小。

二、准备工作

2.1、使用paddleseg配置文件开发

参考:20分钟快速上手PaddleSeg

也可参考官方aistudio:10分钟上手PaddleSeg

# 这里准备好了paddleseg套件,直接解压即可
!unzip -oq /home/aistudio/data/data181375/PaddleSeg.zip -d /home/aistudio/work/

2.2、解压数据集

!unzip -oq /home/aistudio/data/data178466/入侵肾脏.zip -d /home/aistudio/work/dataset/

2.3、安装依赖

!pip install rasterio

三、数据集的处理

3.1、数据集解析:

训练集包括 RLE 编码和未编码 (JSON) 形式的注释。(注释表示肾小球的分割)

我们仅需关注train.csv与train文件夹中的文件

文件架构:

1.JSON 文件的结构如下,每个功能都具有:

A() 和对象类型()。请注意,这些字段在所有文件之间都是相同的,并且不提供信号。typeFeatureidPathAnnotationObject 包含 awithfor 特征的封闭体积geometryPolygoncoordinates 其他,包括图像中要素的名称和颜色。properties 该场在文件类型中是相同的(针对肾小球锁定,为解剖结构解锁),并且不承载信号。IsLocked 请注意,对象本身没有唯一的 ID。给定图像的预期预测是包含图像中所有对象的 RLE 编码掩码。如评估页面中所述,掩码在编码时应为二进制 - 表示缺少遮罩像素,并指示遮罩像素。

2.train.csv包含每个图像的唯一 ID,以及图像中对象的蒙版的 RLE 编码表示形式。

解析train.csv文件的encoding信息
分割出所有TIFF文件内包含肾小球信息的图片
Resize为gcnet网络所需的大小(512x512)

3.HuBMAP-20-dataset_information.csv包含有关每个图像的其他信息(包括匿名患者数据)。

注意:在此次项目中无需用到HuBMAP-20-dataset_information.csv

数据集处理参考

from PIL import Image as PilImage
import cv2
import paddle
import numpy as np
import os
from paddle.io import Dataset
from paddle.vision.transforms import transforms as T

import io
import scipy.misc as misc
import pandas as pd
import pathlib, sys, os, random, time
import numba, cv2, gc
import matplotlib.pyplot as plt
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
from tqdm.notebook import tqdm
# import albumentations as A 图像增强
import rasterio
from rasterio.windows import Window

DATA_PATH = '/home/aistudio/work/dataset'
EPOCHES = 7
BATCH_SIZE = 3
DEVICE = 'cuda'
# 生成paddleseg可接受的数据集
def rle_encode(im):
    pixels = im.flatten(order = 'F')
    pixels = np.concatenate([[0], pixels, [0]])
    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
    runs[1::2] -= runs[::2]
    return ' '.join(str(x) for x in runs)

def rle_decode(mask_rle, shape=(256, 256)):
    s = mask_rle.split()
    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]
    starts -= 1
    ends = starts + lengths
    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
    for lo, hi in zip(starts, ends):
        img[lo:hi] = 1
    return img.reshape(shape, order='F')

@numba.njit()
def rle_numba(pixels):
    size = len(pixels)
    points = []
    if pixels[0] == 1:
        flag = False
        points.append(1)
    else:
        flag = True
    for i in range(1, size):
        if pixels[i] != pixels[i-1]:
            if flag:
                points.append(i+1)
                flag = False
            else:
                points.append(i+1 - points[-1])
                flag = True
    if pixels[-1] == 1: points.append(size-points[-1]+1)    
    return points

def rle_numba_encode(image):
    pixels = image.flatten(order = 'F')
    points = rle_numba(pixels)
    return ' '.join(str(x) for x in points)

def make_grid(shape, window=256, min_overlap=32):
    x, y = shape
    nx = x // (window - min_overlap) + 1
    x1 = np.linspace(0, x, num=nx, endpoint=False, dtype=np.int64)
    x1[-1] = x - window
    x2 = (x1 + window).clip(0, x)
    ny = y // (window - min_overlap) + 1
    y1 = np.linspace(0, y, num=ny, endpoint=False, dtype=np.int64)
    y1[-1] = y - window
    y2 = (y1 + window).clip(0, y)
    slices = np.zeros((nx,ny, 4), dtype=np.int64)
    
    for i in range(nx):
        for j in range(ny):
            slices[i,j] = x1[i], x2[i], y1[j], y2[j]    
    return slices.reshape(nx*ny,4)

identity = rasterio.Affine(1, 0, 0, 0, 1, 0)

class HubDataset(Dataset):

    def __init__(self, root_dir, transform,
                 window=256, overlap=32, threshold = 100):
        self.path = pathlib.Path(root_dir)
        self.overlap = overlap
        self.window = window
        self.transform = transform
        self.csv = pd.read_csv((self.path / 'train.csv').as_posix(),
                               index_col=[0])
        self.threshold = threshold
        self.build_slices()
        self.len = len(self.slices)
        self.as_tensor = T.Compose([
            T.ToTensor(),
            T.Normalize([0.625, 0.448, 0.688],
                        [0.131, 0.177, 0.101]),
        ])
    def build_slices(self):
        self.masks = []
        self.files = []
        self.slices = []
        for i, filename in enumerate(self.csv.index.values):
            filepath = (self.path /'train'/(filename+'.tiff')).as_posix()
            self.files.append(filepath)
            with rasterio.open(filepath, transform = identity) as dataset:
                self.masks.append(rle_decode(
                    self.csv.loc[filename, 'encoding'], dataset.shape))
                slices = make_grid(dataset.shape, window=self.window,
                                   min_overlap=self.overlap)
                for slc in slices:
                    x1,x2,y1,y2 = slc
                    if self.masks[-1][x1:x2,y1:y2].sum() > self.threshold:
                        self.slices.append([i,x1,x2,y1,y2])
                        
    def __getitem__(self, index):
        
        idx = self.slices[index][0]
        filename = self.files[idx]
        x1,x2,y1,y2 = self.slices[index][1:] 
        with rasterio.open(filename, transform = identity) as dataset:
            image = dataset.read([1,2,3],
                        window=Window.from_slices((x1,x2),(y1,y2)))
            image = np.moveaxis(image, 0, -1)
            
        mask = self.masks[idx][x1:x2,y1:y2]
        
        augments = {"image":image, "mask":mask}
        return self.as_tensor(augments['image']), augments['mask'][None]
    
    def __len__(self):
        return self.len

/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/__init__.py:107: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import MutableMapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/rcsetup.py:20: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Iterable, Mapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/colors.py:53: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Sized

3.2、处理成paddleseg可接受的数据集格式

因为格式为tiff的原图的像素高达11919 * 8796,无法输入神经网络中去训练,所以需要经处理后生成了训练用的512*512的图像和标签:

image-110和label-110:


### 大概需要半小时左右,请耐心等待
!ls
%mkdir -p /home/aistudio/custom_dataset/images
%mkdir -p /home/aistudio/custom_dataset/labels
!pwd
WINDOW=1024
MIN_OVERLAP=32
NEW_SIZE=512

trfm = T.Compose([
    T.Resize(NEW_SIZE,NEW_SIZE),
    # T.HorizontalFlip(p=0.5),
    # T.ColorJitter(brightness=0.07, contrast=0.07, saturation=0.1, hue=0.1)
])

dataset = HubDataset(DATA_PATH, window=WINDOW, overlap=MIN_OVERLAP, transform=trfm)

image, mask = dataset[2]

i=0
for image,mask in dataset:
    image_ins=image[0]
    mask_ins=mask[0]

    plt.figure(figsize=(512,512),dpi=1)
    plt.gca().xaxis.set_major_locator(plt.NullLocator())
    plt.gca().yaxis.set_major_locator(plt.NullLocator())
    plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0)
    plt.margins(0,0)
    plt.imshow(image_ins)
    plt.savefig(r'/home/aistudio/custom_dataset/images/%s%d.png' % ('image',i) ,pad_inches=0)  # dpi=100 和上文相对应 pixel尺寸/dpi=inch尺寸
    plt.clf()

    plt.figure(figsize=(512,512),dpi=1)
    plt.gca().xaxis.set_major_locator(plt.NullLocator())
    plt.gca().yaxis.set_major_locator(plt.NullLocator())
    plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0)
    plt.margins(0,0)
    plt.imshow(mask_ins)
    plt.savefig(r'/home/aistudio/custom_dataset/labels/%s%d.png' % ('image',i) ,pad_inches=0)  # dpi=100 和上文相对应 pixel尺寸/dpi=inch尺寸
    plt.clf()   
    i += 1

3.3、将标签图片转换成灰度图以便训练

将所有 /home/aistudio/custom_dataset/labels/ 中的标签图片变换为灰度图并将灰度图中背景的像素值改为0,肾小球的像素值改为1,这样才能进行2分类

完成转化后训练就可以正常进行了

### 大概需要半小时左右,请耐心等待
import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
Annotations_path='/home/aistudio/custom_dataset/labels/'
for filename in os.listdir(Annotations_path):
    if "checkpoints" in filename:
        continue
    label_path=Annotations_path+filename
    img=cv2.imdecode(np.fromfile(label_path, dtype=np.uint8), -1)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    for i in range (512):
        for j in range (512):
            if img[i][j]<127:
                img[i][j]=0
            else:
                img[i][j]=1
    cv2.imwrite(label_path, img)

3.4、划分数据集

%cd work/PaddleSeg
!python tools/split_dataset_list.py /home/aistudio/custom_dataset images labels --split 0.9 0.1 0 --format png png

四、模型训练

4.1、模型和backbone的选取

这里选用的模型是GCnet

一张图片中各个像素点间的关系,特别是长距离像素点间的关系对各种视觉任务非常重要。这种关系可以通过传统的堆叠卷积层的方法获得,但是往往效率低下。Non-local网络通过自注意力机制解决了这个问题。但是对于图片来说,作者通过实验发现,每个像素点的全局特征图基本都是一样的,如图1所示,其中红点是要计算的目标点。基于此,就没有必要计算每个点的全局特征图了。所以,作者就提出了一种比non-local的方法效果好但是计算量小的多的简化的注意力机制模块。

图1 non-local方法计算的红色点的注意力特征图

Non-local的计算方法有多种,包括高斯、嵌入式高斯、点积和拼接。GCNet采用了嵌入式高斯的方法,这也是通常使用的方法。GCNet模块的实现细节示意图如图2所示。其中,图2中的a表示GCNet模块的流程图,图2中的b表示简化版的non-local模块示意图,图2中的c表示GCNet模块示意图。可以看出,GCNet模块是简化版的non-local和SE模块机制的结合。

图2 GCNet模块实现细节示意图

详细模型信息可前往/home/aistudio/work/PaddleSeg/models/gcnet.py查看代码细节

配置文件为/home/aistudio/work/PaddleSeg/configs/gcnet/gcnet_resnet50_os8_voc12aug_512x512_40k.yml

backbone为经典的resnet50

在paperwithcode网站上,可看出gcnet在Instance Segmentation on COCO test-dev上mask_AP高达45.4,算是比较不错的模型

4.2、数据集的加载

相关的pascal_voc12aug.yml和pascal_voc12.yml配置文件在/home/aistudio/work/PaddleSeg/configs/_base_路径下

这里pascal_voc12aug.yml的train_dataset: mode:要改成train,不然会报错

以下是数据集的详细加载的配置文件pascal_voc12.yml

4.3、开始训练

笔者是用V100 32G跑的!!!若你选的是V100 16G的显卡导致运行时显存不够,请在pascal_voc12.yml配置文件调低batch_size

若不想花费时间训练,想直接看效果,笔者在/home/aistudio/work/PaddleSeg/output文件夹下保存有best_model,可直接在下面进行模型验证和模型预测

%cd /home/aistudio/work/PaddleSeg
!python train.py \
       --config configs/gcnet/gcnet_resnet50_os8_voc12aug_512x512_40k.yml \
       --do_eval \
       --use_vdl \
       --save_interval 500 \
       --save_dir output
# --config指定配置文件。
# --save_interval指定每训练特定轮数后,就进行一次模型保存或者评估(如果开启模型评估)。
# --do_eval开启模型评估。具体而言,在训练save_interval指定的轮数后,会进行模型评估。
# --use_vdl开启写入VisualDL日志信息,用于VisualDL可视化训练过程。
# --save_dir指定模型和visualdl日志文件的保存根路径。

训练过程可视化

模型保存路径为/home/aistudio/work/PaddleSeg/output
output

├── iter_500 #表示在500步保存一次模型

├── model.pdparams  #模型参数

└── model.pdopt     #训练阶段的优化器参数

├── iter_1000 #表示在1000步保存一次模型

├── model.pdparams  #模型参数

└── model.pdopt     #训练阶段的优化器参数

└── best_model #精度最高的模型权重

└── model.pdparams

4.4、模型验证

笔者在/home/aistudio/work/PaddleSeg/output文件夹下保存有best_model,可直接在下面进行模型验证和模型预测

%cd /home/aistudio/work/PaddleSeg
!python val.py \
       --config configs/gcnet/gcnet_resnet50_os8_voc12aug_512x512_40k.yml \
       --model_path output/best_model/model.pdparams \
       --is_slide \
       --crop_size 256 256 \
       --stride 128 128
/home/aistudio/work/PaddleSeg
2022-12-05 16:57:58 [INFO]	
---------------Config Information---------------
batch_size: 18
iters: 40000
loss:
  coef:
  - 1
  - 0.4
  types:
  - type: CrossEntropyLoss
lr_scheduler:
  end_lr: 1.0e-05
  learning_rate: 0.01
  power: 0.9
  type: PolynomialDecay
model:
  align_corners: false
  backbone:
    output_stride: 8
    pretrained: https://bj.bcebos.com/paddleseg/dygraph/resnet50_vd_ssld_v2.tar.gz
    type: ResNet50_vd
  enable_auxiliary_loss: true
  gc_channels: 512
  pretrained: null
  ratio: 0.25
  type: GCNet
optimizer:
  momentum: 0.9
  type: sgd
  weight_decay: 4.0e-05
train_dataset:
  dataset_root: /home/aistudio/custom_dataset
  mode: train
  num_classes: 2
  train_path: /home/aistudio/custom_dataset/train.txt
  transforms:
  - crop_size:
    - 512
    - 512
    type: RandomPaddingCrop
  - brightness_range: 0.4
    contrast_range: 0.4
    saturation_range: 0.4
    type: RandomDistort
  - type: Normalize
  type: Dataset
val_dataset:
  dataset_root: /home/aistudio/custom_dataset
  mode: val
  num_classes: 2
  transforms:
  - type: Normalize
  type: Dataset
  val_path: /home/aistudio/custom_dataset/val.txt
------------------------------------------------
W1205 16:57:58.251394 31164 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1205 16:57:58.251439 31164 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
2022-12-05 16:57:59 [INFO]	Loading pretrained model from https://bj.bcebos.com/paddleseg/dygraph/resnet50_vd_ssld_v2.tar.gz
2022-12-05 16:58:00 [INFO]	There are 275/275 variables loaded into ResNet_vd.
2022-12-05 16:58:00 [INFO]	Loading pretrained model from output/best_model/model.pdparams
2022-12-05 16:58:00 [INFO]	There are 311/311 variables loaded into GCNet.
2022-12-05 16:58:00 [INFO]	Loaded trained params of model successfully
2022-12-05 16:58:00 [INFO]	Start evaluating (total_samples: 177, total_iters: 177)...
177/177 [==============================] - 60s 337ms/step - batch_cost: 0.3368 - reader cost: 9.8407e-04
2022-12-05 16:59:00 [INFO]	[EVAL] #Images: 177 mIoU: 0.8941 Acc: 0.9827 Kappa: 0.8837 Dice: 0.9418
2022-12-05 16:59:00 [INFO]	[EVAL] Class IoU: 
[0.9814 0.8068]
2022-12-05 16:59:00 [INFO]	[EVAL] Class Precision: 
[0.9882 0.919 ]
2022-12-05 16:59:00 [INFO]	[EVAL] Class Recall: 
[0.9931 0.8686]

4.5、模型预测

笔者在/home/aistudio/work/PaddleSeg/output文件夹下保存有best_model,可直接在下面进行模型验证和模型预测

%cd /home/aistudio/work/PaddleSeg
!python predict.py \
       --config configs/gcnet/gcnet_resnet50_os8_voc12aug_512x512_40k.yml \
       --model_path output/best_model/model.pdparams \
       --image_path /home/aistudio/custom_dataset/images/image1034.png \
       --save_dir output/result

# 其中image_path可以是一个图片路径,也可以是一个目录。如果是一个目录,将对目录内的所有图片进行预测并保存可视化结果图
# 结果保存在/home/aistudio/work/PaddleSeg/output/result
/home/aistudio/work/PaddleSeg
2022-12-05 17:00:47 [INFO]	
---------------Config Information---------------
batch_size: 18
iters: 40000
loss:
  coef:
  - 1
  - 0.4
  types:
  - type: CrossEntropyLoss
lr_scheduler:
  end_lr: 1.0e-05
  learning_rate: 0.01
  power: 0.9
  type: PolynomialDecay
model:
  align_corners: false
  backbone:
    output_stride: 8
    pretrained: https://bj.bcebos.com/paddleseg/dygraph/resnet50_vd_ssld_v2.tar.gz
    type: ResNet50_vd
  enable_auxiliary_loss: true
  gc_channels: 512
  pretrained: null
  ratio: 0.25
  type: GCNet
optimizer:
  momentum: 0.9
  type: sgd
  weight_decay: 4.0e-05
train_dataset:
  dataset_root: /home/aistudio/custom_dataset
  mode: train
  num_classes: 2
  train_path: /home/aistudio/custom_dataset/train.txt
  transforms:
  - crop_size:
    - 512
    - 512
    type: RandomPaddingCrop
  - brightness_range: 0.4
    contrast_range: 0.4
    saturation_range: 0.4
    type: RandomDistort
  - type: Normalize
  type: Dataset
val_dataset:
  dataset_root: /home/aistudio/custom_dataset
  mode: val
  num_classes: 2
  transforms:
  - type: Normalize
  type: Dataset
  val_path: /home/aistudio/custom_dataset/val.txt
------------------------------------------------
W1205 17:00:47.574730 31710 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1205 17:00:47.574776 31710 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
2022-12-05 17:00:48 [INFO]	Loading pretrained model from https://bj.bcebos.com/paddleseg/dygraph/resnet50_vd_ssld_v2.tar.gz
2022-12-05 17:00:49 [INFO]	There are 275/275 variables loaded into ResNet_vd.
2022-12-05 17:00:49 [INFO]	Number of predict images = 1
2022-12-05 17:00:49 [INFO]	Loading pretrained model from output/best_model/model.pdparams
2022-12-05 17:00:49 [INFO]	There are 311/311 variables loaded into GCNet.
2022-12-05 17:00:49 [INFO]	Start to predict...
1/1 [==============================] - 1s 1s/step

11/311 variables loaded into GCNet.
2022-12-05 17:00:49 [INFO] Start to predict…
1/1 [==============================] - 1s 1s/step

五、模型评估

因这个数据集的来源于kaggle,且未公开测试集的分割信息,故我们只能使用划分的那10%的验证集来进行评估,若有兴趣看一下数据集中的测试集的分割情况,可以试着将测试集的多个tiff文件裁剪成许多张512*512的图片,然后拿去做预测,当然只有懂肾小球的方面的人才能看出模型的好坏,这就有点偏主观性了

最终跑了7000个iter炼出的模型在10%的验证集上的评估指标如下:

请点击此处查看本环境基本用法.

Please click here for more detailed instructions.

此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐