作者 | Insaf Ashrapov
来源 | googleblog
编辑 | 代码医生团队
在本文中,将展示如何编写自己的数据生成器以及如何使用albumentations作为扩充库。与segmentation_models库一起,它为Unet和其他类似unet的架构提供了数十个预训练。有关完整代码,请访问Github。
https://github.com/Diyago/ML-DL-scripts/tree/master/DEEP%20LEARNING/segmentation/Segmentation%20pipeline
理论
语义图像分割的任务是用相应的所表示的类标记图像的每个像素。对于这样的任务,具有不同改进的Unet架构已经显示出最佳结果。它背后的核心思想只是几个卷积块,它们提取深度和不同类型的图像特征,接着是所谓的反卷积或上采样块,它们恢复了输入图像的初始形状。除了在每个卷积层之后,还有一些跳过连接,这有助于网络记住初始图像并帮助防止渐变渐变。有关更多详细信息,请阅读arxiv文章。
https://arxiv.org/abs/1505.04597
数据集 - 卫星图像
对于分段,不需要太多数据就能获得不错的结果,即使是100张带注释的照片也足够了。目前,将使用来自Massachusetts Roads Dataset ,大约有1100多个带注释的列车图像,它们甚至提供验证和测试数据集。不幸的是,没有下载按钮,所以必须使用脚本。此脚本将完成工作(可能需要一些时间才能完成)。
https://www.cs.toronto.edu/~vmnih/data/
https://gist.github.com/Diyago/83919fcaa9ca46e4fcaa632009ec2dbd
来看看图片示例:
马萨诸塞州道路数据集图像和地面真相掩模ex。
注释和图像质量似乎相当不错,网络应该能够检测道路。
库安装
首先,需要安装带有TensorFlow的Keras。对于Unet构造,将使用Pavel Yakubovskiy的库名为segmentation_models,用于数据扩充albumentation库。稍后会详细介绍它们。两个库都经常更新,所以更喜欢直接从git更新它们。
conda install -c conda-forge keras
pip install git + https://github.com/qubvel/efficientnet
pip install git + https://github.com/qubvel/classification_models.git
pip install git + https://github.com/qubvel/segmentation_models
pip install git + https://github.com/albu/albumentations
pip install tta-wrapper
定义数据生成器
作为数据生成器,将使用自定义生成器。它应该继承keras.utils.Sequence并且应该定义这样的方法:
使用自定义生成器的一个主要优点是,可以使用拥有的每种格式数据,并且可以执行任何操作 - 只是不要忘记为keras生成所需的输出(批处理)。
这里定义__init__方法。它的主要部分是为图像设置路径(self.image_filenames)和掩码名称(self.mask_names)。不要忘记对它们进行排序,因为对于self.image_filenames [i]相应的掩码应该是self.mask_names [i]。
def __init__(self, root_dir=r'../data/val_test', image_folder='img/', mask_folder='masks/',
batch_size=1, image_size=768, nb_y_features=1,
augmentation=None,
suffle=True):
self.image_filenames = listdir_fullpath(os.path.join(root_dir, image_folder))
self.mask_names = listdir_fullpath(os.path.join(root_dir, mask_folder))
self.batch_size = batch_size
self.augmentation = augmentation
self.image_size = image_size
self.nb_y_features = nb_y_features
self.suffle = suffle
def listdir_fullpath(d):
return np.sort([os.path.join(d, f) for f in os.listdir(d)])
下一件重要的事__getitem__。通常,不能将所有图像存储在RAM中,因此每次生成新的一批数据时,都应该读取相应的图像。下面定义训练方法。为此创建一个空的numpy数组(np.empty),它将存储图像和掩码。然后通过read_image_mask方法读取图像,将增强应用到每对图像和蒙版中。最后返回批处理(X,y),它已准备好安装到网络中。
def __getitem__(self, index):
data_index_min = int(index*self.batch_size)
data_index_max = int(min((index+1)*self.batch_size, len(self.image_filenames)))
indexes = self.image_filenames[data_index_min:data_index_max]
this_batch_size = len(indexes) # The last batch can be smaller than the others
X = np.empty((this_batch_size, self.image_size, self.image_size, 3), dtype=np.float32)
y = np.empty((this_batch_size, self.image_size, self.image_size, self.nb_y_features), dtype=np.uint8)
for i, sample_index in enumerate(indexes):
X_sample, y_sample = self.read_image_mask(self.image_filenames[index * self.batch_size + i],
self.mask_names[index * self.batch_size + i])
# if augmentation is defined, we assume its a train set
if self.augmentation is not None:
# Augmentation code
augmented = self.augmentation(self.image_size)(image=X_sample, mask=y_sample)
image_augm = augmented['image']
mask_augm = augmented['mask'].reshape(self.image_size, self.image_size, self.nb_y_features)
# divide by 255 to normalize images from 0 to 1
X[i, ...] = image_augm/255
y[i, ...] = mask_augm
else:
...
return X, y
接下来,定义生成器,它们将被送入网络:
test_generator = DataGeneratorFolder(root_dir = './data/road_segmentation_ideal/training',
image_folder = 'input/',
mask_folder = 'output/',
nb_y_features = 1)
train_generator = DataGeneratorFolder(root_dir = './data/road_segmentation_ideal/training',
image_folder = 'input/',
mask_folder = 'output/',
batch_size=4,
image_size=512,
nb_y_features = 1, augmentation = aug_with_crop)
数据增强- albumentations
数据增强是一种策略,可以显着增加可用于训练模型的数据的多样性,而无需实际收集新数据。它有助于防止过度拟合并使模型更加健壮。
有很多用于此类任务的库:imaging,augmentor,solt,keras / pytorch的内置方法,或者可以使用OpenCV库编写自定义扩充。但我强烈推荐albumentations库。它使用起来非常快速和方便。有关用法示例,请访问官方存储库或查看示例笔记本。
https://github.com/albu/albumentations
https://github.com/albu/albumentations/tree/master/notebooks
在任务中,将使用基本的扩充,例如翻转和对比与非平凡的ElasticTransform。可以在上图中的示例。
def aug_with_crop(image_size = 256, crop_prob = 1):
return Compose([
RandomCrop(width = image_size, height = image_size, p=crop_prob),
HorizontalFlip(p=0.5),
VerticalFlip(p=0.5),
RandomRotate90(p=0.5),
Transpose(p=0.5),
ShiftScaleRotate(shift_limit=0.01, scale_limit=0.04, rotate_limit=0, p=0.25),
RandomBrightnessContrast(p=0.5),
RandomGamma(p=0.25),
IAAEmboss(p=0.25),
Blur(p=0.01, blur_limit = 3),
OneOf([
ElasticTransform(p=0.5, alpha=120, sigma=120 * 0.05, alpha_affine=120 * 0.03),
GridDistortion(p=0.5),
OpticalDistortion(p=1, distort_limit=2, shift_limit=0.5)
], p=0.8)
], p = 1)
定义所需的扩充后,可以轻松获得输出:
augmented = aug_with_crop(image_size = 1024)(image=img, mask=mask)
image_aug = augmented['image']
mask_aug = augmented['mask']
回调
将使用常见的回调:
https://www.tensorflow.org/tensorboard/r2/scalars_and_keras
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping, TensorBoard
# reduces learning rate on plateau
lr_reducer = ReduceLROnPlateau(factor=0.1,
cooldown= 10,
patience=10,verbose =1,
min_lr=0.1e-5)
# model autosave callbacks
mode_autosave = ModelCheckpoint("./weights/road_crop.efficientnetb0imgsize.h5",
monitor='val_iou_score',
mode='max', save_best_only=True, verbose=1, period=10)
# stop learining as metric on validatopn stop increasing
early_stopping = EarlyStopping(patience=10, verbose=1, mode = 'auto')
# tensorboard for monitoring logs
tensorboard = TensorBoard(log_dir='./logs/tenboard', histogram_freq=0,
write_graph=True, write_images=False)
callbacks = [mode_autosave, lr_reducer, tensorboard, early_stopping]
训练
作为模型,将使用Unet。最简单的使用方法是从segmentation_models库中获取。
https://github.com/qubvel/segmentation_models
初始化Unet模型后,应该编译它。此外将IOU(交叉联合)设置为将监控的度量和bce_jaccard_loss(二进制交叉熵加jaccard损失)作为将优化的损失。
model = Unet(backbone_name = 'efficientnetb0', encoder_weights='imagenet', encoder_freeze = False)
model.compile(optimizer = Adam(), loss=bce_jaccard_loss, metrics=[iou_score])
history = model.fit_generator(train_generator, shuffle =True,
epochs=50, workers=4, use_multiprocessing=True,
validation_data = test_generator,
verbose = 1, callbacks=callbacks)
开始训练后,可以查看张量板日志。可以很好地看到模型训练,即使在50个时代之后也没有达到全球/地方的最佳状态。
Tensorboard日志
损失和IOU指标历史记录
推理
因此在验证时有0.558 IOU,但是每个像素预测都高于0,将其视为掩码。通过选择适当的阈值,可以进一步将结果提高0.039(7%)。
验证阈值调整
度量标准确实非常有趣,但更具洞察力的模型预测。从下面的图片中看到网络很好地完成了任务,这很棒。对于推理代码和计算指标,可以阅读完整代码。
https://github.com/Diyago/ML-DL-scripts/tree/master/DEEP%20LEARNING/segmentation/Segmentation%20pipeline