【问题标题】:Weighting samples in multiclass image segmentation using keras使用 keras 对多类图像分割中的样本进行加权
【发布时间】:2020-02-16 20:52:13
【问题描述】:

我正在使用基于 Unet 的模型对生物医学图像执行图像分割。每个图像都是 224x224,我有四个类,包括背景类。每个掩码的大小为 (224x224x4),因此我的生成器创建了一批大小为 (16x224x224x4) 的 numpy 数组。我将掩码的值重新转换为 1 或 0,因此对于每个类,相关通道中都存在 1。图像也按 1/255 缩放。我使用骰子分数作为训练期间的性能指标,使用 1-dice 分数作为损失函数。我似乎在训练期间获得了高达 0.89 的分数,但我发现当我在测试集上进行预测时,我总是在预测背景类。我只在几百张图像上训练了 10 个 epoch(尽管我确实可以访问更多),这可能会影响模型,但我原以为我仍然会得到其他类的预测,所以我假设主要问题是阶级不平衡。从在线查看 sample_weight 参数可能是答案,但我不确定我打算如何实现实际的重量部分?大概我需要使用层在模型中的某个点将权重应用于像素数组,但我不确定如何。任何帮助将不胜感激?

class DataGenerator(keras.utils.Sequence):
     def __init__(self, imgIds, maskIds, imagePath, maskPath, batchSize=16, imageSize = (224, 224, 3), nClasses=2, shuffle=False):
       self.imgIds = imgIds
       self.maskIds = maskIds
       self.imagePath = imagePath
       self.maskPath = maskPath
       self.batchSize = batchSize
       self.imageSize = imageSize
       self.nClasses = nClasses
       self.shuffle = shuffle


     def __load__(self, imgName, maskName):

       img = cv2.imread(os.path.join(self.imagePath,imgName))
       img = cv2.resize(img, (self.imageSize[0], self.imageSize[1]))

       mask = cv2.imread(os.path.join(self.maskPath,maskName))
       mask = np.dstack((mask, np.zeros((4000, 4000))))

       mask[:,:,3][mask[:,:,0]==0]=255
       mask = mask.astype(np.bool)
       mask = img_as_bool(resize(mask, (self.imageSize[0], self.imageSize[1])))
       mask = mask.astype('uint8')

       img = img/255.0
       mask = mask

       return (img, mask)


    def __getitem__(self, index):

       if(index+1)*self.batchSize > len(self.imgIds):
          self.batchSize = len(self.imgIds) - index*self.batchSize

       batchImgs = self.imgIds[self.batchSize*index:self.batchSize*(index+1)]
       batchMasks = self.maskIds[self.batchSize*index:self.batchSize*(index+1)]

       batchfiles = [self.__load__(imgFile, maskFile) for imgFile, maskFile in 
       zip(batchImgs, batchMasks)]

       images, masks = zip(*batchfiles)

       return np.array(list(images)), np.array(list(masks))


   def __len__(self):
       return int(np.ceil(len(self.imgIds)/self.batchSize))


class Unet():
   def __init__(self, imgSize):
       self.imgSize = imgSize


   def convBlocks(self, x, filters, kernelSize=(3,3), padding='same', strides=1):

       x = keras.layers.BatchNormalization()(x)
       x = keras.layers.Activation('relu')(x)
       x = keras.layers.Conv2D(filters, kernelSize, padding=padding, strides=strides)(x)

       return x


   def identity(self, x, xInput, f, padding='same', strides=1):

      skip = keras.layers.Conv2D(f, kernel_size=(1, 1), padding=padding, strides=strides)(xInput)
      skip = keras.layers.BatchNormalization()(skip)
      output = keras.layers.Add()([skip, x])

      return output


    def residualBlock(self, xIn, f, stride):

      res = self.convBlocks(xIn, f, strides=stride)
      res = self.convBlocks(res, f, strides=1)
      output = self.identity(res, xIn, f, strides=stride)

      return output


    def upSampling(self, x, xInput):

      x = keras.layers.UpSampling2D((2,2))(x)
      x = keras.layers.Concatenate()([x, xInput])

      return x


    def encoder(self, x, filters, kernelSize=(3,3), padding='same', strides=1):

      e1 = keras.layers.Conv2D(filters[0], kernelSize, padding=padding, strides=strides)(x)
      e1 = self.convBlocks(e1, filters[0])

      shortcut = keras.layers.Conv2D(filters[0], kernel_size=(1, 1), padding=padding, strides=strides)(x)
      shortcut = keras.layers.BatchNormalization()(shortcut)
      e1Output = keras.layers.Add()([e1, shortcut])

      e2 = self.residualBlock(e1Output, filters[1], stride=2)
      e3 = self.residualBlock(e2, filters[2], stride=2)
      e4 = self.residualBlock(e3, filters[3], stride=2)
      e5 = self.residualBlock(e4, filters[4], stride=2)

      return e1Output, e2, e3, e4, e5


  def bridge(self, x, filters):

      b1 = self.convBlocks(x, filters, strides=1)
      b2 = self.convBlocks(b1, filters, strides=1)

      return b2


  def decoder(self, b2, e1, e2, e3, e4, filters, kernelSize=(3,3), padding='same', strides=1):

      x = self.upSampling(b2, e4)
      d1 = self.convBlocks(x, filters[4])
      d1 = self.convBlocks(d1, filters[4])
      d1 = self.identity(d1, x, filters[4])

      x = self.upSampling(d1, e3)
      d2 = self.convBlocks(x, filters[3])
      d2 = self.convBlocks(d2, filters[3])
      d2 = self.identity(d2, x, filters[3])

      x = self.upSampling(d2, e2)
      d3 = self.convBlocks(x, filters[2])
      d3 = self.convBlocks(d3, filters[2])
      d3 = self.identity(d3, x, filters[2])

      x = self.upSampling(d3, e1)
      d4 = self.convBlocks(x, filters[1])
      d4 = self.convBlocks(d4, filters[1])
      d4 = self.identity(d4, x, filters[1])

      return d4 


  def ResUnet(self, filters = [16, 32, 64, 128, 256]):

      inputs = keras.layers.Input((224, 224, 3))

      e1, e2, e3, e4, e5 = self.encoder(inputs, filters)
      b2 = self.bridge(e5, filters[4])
      d4 = self.decoder(b2, e1, e2, e3, e4, filters)

      x = keras.layers.Conv2D(4, (1, 1), padding='same', activation='softmax')(d4)
      model = keras.models.Model(inputs, x)

      return model


imagePath = 'output/t2'
maskPath = 'output/t1'

imgIds = glob.glob(os.path.join(imagePath, '*'))
maskIds = glob.glob(os.path.join(maskPath, '*'))

imgIds = [os.path.basename(f) for f in imgIds]
maskIds = [os.path.basename(f) for f in maskIds]

trainImgIds = imgIds[:300]
trainMaskIds = maskIds[:300]
validImgIds = imgIds[300:350]
validMaskIds = maskIds[300:350]

trainGenerator = DataGenerator(trainImgIds, trainMaskIds, imagePath, maskPath, **params)
validGenerator = DataGenerator(validImgIds, validMaskIds, imagePath, maskPath)

trainSteps = len(trainImgIds)//trainGenerator.batchSize
validSteps = len(validImgIds)//validGenerator.batchSize

unet = Unet(224)
model = unet.ResUnet()
model.summary()

adam = keras.optimizers.Adam()
model.compile(optimizer=adam, loss=dice_coef_loss, metrics=[dice_coef])

hist = model.fit_generator(trainGenerator, validation_data=validGenerator, 
steps_per_epoch=trainSteps, validation_steps=validSteps, 
                verbose=1, epochs=6)

【问题讨论】:

  • 解决方案可以在这里找到:[Keras - how to use class_weight with 3D data #3653][1] 如果你弄清楚如何实际实现这个,语法,请把它放在一个答案中在这里,因为我没有时间弄清楚这一点并且也可以使用它。 [1]:github.com/keras-team/keras/issues/3653

标签: deep-learning conv-neural-network image-segmentation tf.keras weighting


【解决方案1】:

为了跟进这件事,我使用 sample_weight 让它工作。如果你知道你必须做什么,那就太好了。不幸的是,文档对此并不十分清楚,大概是因为此功能最初是为时间序列数据添加的。

  • 在指定模型时,您需要在损失函数之前将 2D 图像大小的输出重塑为向量。
  • 在编译模型时使用 sample_weight_mode="temporal"。这将允许您传入一个权重矩阵进行训练,其中每一行代表单个样本的权重向量。

希望对你有帮助。

【讨论】:

  • 我将在哪里传递权重矩阵?权重向量是否等于我们的 2D 图像大小输出的形状,因此行中的每个元素都包含属于相应像素类的权重?
【解决方案2】:

我正在使用 Keras
它不是特别是样本权重。
首先,您最好转换为灰度图像和
您需要重新设计问题架构,如下所示:
构建两个模型:

1. 检测和分割图像像素或感兴趣区域 (ROI) 的分割模型 - 无论类别类型如何,您都可以将其提取为补丁。
假设您的 ROI 是值为 1(正)的像素,那么值 0(负)的背景很可能是像素的主要类别,因此它是不平衡的数据,因此您需要使用损失函数来更多地惩罚假阴性而不是误报,比如balanced_cross_entropy:

def balanced_cross_entropy(beta):
  def convert_to_logits(y_pred):
      y_pred = tf.clip_by_value(y_pred, tf.keras.backend.epsilon(), 1 - tf.keras.backend.epsilon())

      return tf.log(y_pred / (1 - y_pred))

  def loss(y_true, y_pred):
    y_pred = convert_to_logits(y_pred)
    pos_weight = beta / (1 - beta)
    loss = tf.nn.weighted_cross_entropy_with_logits(logits=y_pred, targets=y_true, pos_weight=pos_weight)

    # or reduce_sum and/or axis=-1
    return tf.reduce_mean(loss * (1 - beta))

  return loss

然后在您的模型中,对负像素使用 20% 的权重,对正像素使用 80% 的权重,或者根据需要进行调整。

model.compile(optimizer=Adam(), loss=balanced_cross_entropy(0.2), metrics=["accuracy"])
  1. 一种分类器模型,用于处理自动编码器模型检测到的 ROI 或提取的补丁,并在对标记的补丁进行训练后检测(现在)3 个类别中的类别类型。

  2. 对于第一部分,您可以选择添加阈值模块。

  3. 如果您的某些类别数据代表性不足,例如您的类别 3(索引 2)很少见,那么样本权重将在分类器模型中很有用,那么您可以为类别 4 的图像分配更多权重,或者您可以使用class_weight:

    class_weights = {0: 0.1, 1: 0.1, 2: 0.8}
    model.fit_generator(train_gen, class_weight=class_weights)
    

    您也可以使用数据增强技术

  4. 要使用自定义损失函数加载保存的模型,请使用自定义对象:

     model = load_model(filePath, 
              custom_objects={'loss': balanced_cross_entropy(0.2)})
    

【讨论】:

  • 我觉得这有点令人困惑。我的问题是多类的,我应该能够训练模型以找到多个类,同时考虑需要一个加权损失函数来抵消所有类之间的不平衡。自动编码器从何而来,这是为了构建掩码吗?什么是阈值模块?而且我最后使用的分类模型需要完全卷积,但对于分割问题,我们不能使用 class_weight 参数?
  • Auto-Encoder 是 U-NET,用它来分割感兴趣的区域,你可以使用加权损失函数,否则你的模型会将像素分类为背景。然后你得到这些片段并将其传递给另一个深度卷积神经网络,将其分类为一个或多个类。
猜你喜欢
  • 2021-04-29
  • 2020-04-18
  • 2020-10-12
  • 1970-01-01
  • 2019-08-08
  • 2019-04-14
  • 2017-07-24
  • 2018-10-19
  • 2018-09-29
相关资源
最近更新 更多