392. Prostate cANcer graDe Assessment (PANDA) Challenge | prostate-cancer-grade-assessment
首先,非常感谢组织者。其次,我要感谢我的团队。我们拥有一个非常积极、鼓舞人心的工作环境。下面您将读到的大部分想法都来自团队的贡献,并由成员们共同分享。
我使用了中等分辨率,唯一做的预处理是去除白色背景,并将中等分辨率的图像存储在 SSD 驱动器上:
# 函数取自 R Guo
def crop_white(image, value: int = 255):
assert image.shape[2] == 3
assert image.dtype == np.uint8
ys, = (image.min((1, 2)) < value).nonzero()
xs, = (image.min(0).min(1) < value).nonzero()
if len(xs) == 0 or len(ys) == 0:
return image
return image[ys.min():ys.max() + 1, xs.min():xs.max() + 1]
就像在 APTOS 竞赛中一样,清除图像中的笔迹标记等是至关重要的。我使用了这篇帖子中的出色工作:https://www.kaggle.com/c/prostate-cancer-grade-assessment/discussion/151323。这也缩小了 CV(交叉验证)和 LB(排行榜)之间的差距。
增强发生在两个层级(切片和图块):
打开活检切片后,我们进行随机填充并应用以下变换之一(类似于 R Guo)。
def get_transforms_train():
transforms=A.Compose(
[
A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.05, rotate_limit=10, border_mode=cv2.BORDER_CONSTANT, p=0.5,value=(255,255,255)),
A.OneOf([
A.Flip(p=0.5),
A.RandomRotate90(p=0.5),
], p=0.3
)
]
)
return transforms
对于每个图块,我使用了标准的 fastai GPU 增强:rotate=(-10, 10),flip vertically (p=0.5)。对于填充,我使用了 reflection,这在 CV 上带来了轻微的提升。
我决定使用一个非常简单的模型 resnet34,但在整个比赛过程中最终做了一些修改。
主要思想建立在 @iafoss 的基础上。在 resnet 编码器之后,我们通过以下方式重塑特征使其看起来像一个正方形:x = x.view(x.shape[0], x.shape[1], x.shape[2]//int(np.sqrt(N)), -1)。这里 N 代表图块数量。之后我们将所有特征传递给 SqueezeExcite 块。
重塑特征后,我们添加了 1 个 SE 块,使网络能够基于图块学习单个切片的特征。
实验由 @cateek 完成
# 代码采用自
# https://github.com/rwightman/pytorch-image-models/tree/master/timm/models
def make_divisible(v, divisor=8, min_value=None):
min_value = min_value or divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# 确保向下舍入不超过 10%。
if new_v < 0.9 * v:
new_v += divisor
return new_v
def sigmoid(x, inplace: bool = False):
return x.sigmoid_() if inplace else x.sigmoid()
class SqueezeExcite(nn.Module):
def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None,
act_layer=nn.ReLU, gate_fn=sigmoid, divisor=1, **_):
super(SqueezeExcite, self).__init__()
self.gate_fn = gate_fn
reduced_chs = make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor)
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True)
self.act1 = act_layer(inplace=True)
self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True)
def forward(self, x):
x_se = self.avg_pool(x)
x_se = self.conv_reduce(x_se)
x_se = self.act1(x_se)
x_se = self.conv_expand(x_se)
x = x * self.gate_fn(x_se)
return x
一旦特征通过 SqueezeExcite �