赞
踩
将卷积神经网络(CNN)应用在图像分割任务上,我们需要对网络结构进行设计。
可供选择的网络有:YOLO v3、Mask R-CNN、U-Net。
在此项目中,我选择的是U-Net。
数据集选择的是 isbi_2012,即可选数据集网站的如下图项:
图像分割就是要将一个图像分割成许多个部分,对于一个分割完毕的图像,每一个像素点都属于且只属于一个部分,同一个部分的所有像素点必须是相邻的,联通的。
因此不难看出,简单的图像检测只需要给出一个物体的大致位置就行,但是图像分割必须给出每一个部分的精确轮廓。
为此可以用一个函数去求出这个轮廓的位置和形状,可以用神经网络算法去求这个函数。
FCN是第一个将全卷积网络与图像分割结合在一起的框架,FCN的框架模型如下图:
FCN采取解决方法是将pool4、pool3、和特征map融合起来,由于pool3、pool4、特征map大小尺寸是不一样的,所以融合应该前上采样到同一尺寸。这里的融合是拼接在一起,不是对应元素相加。
可以看出,由于FCN采取的是全卷积网络,在模型中对图像多次求卷积,在输出层得到的信息,也是对原图进行特征提取后的数据。但是图像分割想要得到的结果是一个和原图大小一样的图像,即一个描述各部分轮廓的黑白图。为了得到这个轮廓图像,必修将输出的特征信息还原成图像。
为此,FCN采取反卷积的方法来还原图像。将所得的特征上采样回去,再将预测结果做一一对应的分类,区分某一点在图像中的意义。因此在还原时,分割问题就转化成了分类问题。
这样会丢失很多信息。
U-Net的框架如下图:
U-Net分为两个部分,特征提取部分和上采样部分。
特征提取部分在图中包括左半边和下边。上采样部分包括右边。由于整个模型看起来像是U字型,所以称为U-Net。
收缩路径就是常规的卷积网络,它包含重复的2个3x3卷积,紧接着是一个RELU,一个max pooling(步长为2),用来降采样,每次降采样我们都将feature channel减半。扩展路径包含一个上采样(2x2上卷积),这样会减半feature channel,接着是一个对应的收缩路径的feature map,然后是2个3x3卷积,每个卷积后面跟一个RELU,因为每次卷积会丢失图像边缘,所以裁剪是有必要的,最后来一个1x1的卷积,用来将有64个元素的feature vector映射到一个类标签,整个网络一共有23个卷积层。
overlap-tile策略是U-Net中使用到的一种优化策略。
由于医学影像很大,一般不能直接作为网络的输入,所以该策略会把训练集的图像分成若干小部分,对每一个部分进行训练。
在将大图像分块的时候,需要对每一个小块求卷积,但是图像边界的像素点没有周围像素,求卷积会导致信息的缺失,因此需要对划分好的每一个小块做一次扩充。采用镜像对称的原理,将矩形小块按其边界进行镜像对称,这样小块的四周会多处一圈和自己对称的部分,避免求卷积导致的信息丢失。
深度神经网络拥有很强的学习能力,因此如果训练数据集的内容不够,训练的数据集太小,会产生过拟合问题,导致训练好的模型无法正确使用。
因此在数据集有限的情况下,需要人为地对数据集进行扩充。比如将图像拉伸,裁剪,旋转,加入各种噪声等。
由于U-Net用于解决细胞组织图像的问题,根据实际情况,细胞组织的边界每时每刻都在做不规则的畸形变换。因此可以对图像加入这种噪声,在适量的范围内,随即改变细胞的边界,改变训练集的图像,从而扩展训练集数据。
代码见:这里
语言:python
python语言方便快捷,出错率小,适合做复杂的算法。
框架选择:tensorflow,keras
其中tensorflow是主要框架,keras只引用其中的图像处理功能,辅助tensorflow完成任务。
此外还用了absl库函数。用absl库定义可重复的代码段,定义训练的各个参数(比如学习率,周期数,每个周期的训练轮数等)。
参数设定:eopch=3(训练三个周期)
step=100(每个周期训练100轮)
batch_size=2(每次训练取两个样本)
learning_rate=0.0001(学习率)
如下是代码中关于网络框架的定义。
inputs = tf.keras.layers.Input((512, 512, 1)) # Contracting part conv1 = tf.keras.layers.Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(inputs) conv1 = tf.keras.layers.Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv1) assert conv1.shape[1:] == (512, 512, 64) pool1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv1) assert pool1.shape[1:] == (256, 256, 64) conv2 = tf.keras.layers.Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool1) conv2 = tf.keras.layers.Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv2) assert conv2.shape[1:] == (256, 256, 128) pool2 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv2) assert pool2.shape[1:] == (128, 128, 128) conv3 = tf.keras.layers.Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool2) conv3 = tf.keras.layers.Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv3) assert conv3.shape[1:] == (128, 128, 256) pool3 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv3) assert pool3.shape[1:] == (64, 64, 256) conv4 = tf.keras.layers.Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool3) conv4 = tf.keras.layers.Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv4) drop4 = tf.keras.layers.Dropout(0.5)(conv4) assert drop4.shape[1:] == (64, 64, 512) pool4 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(drop4) assert pool4.shape[1:] == (32, 32, 512) conv5 = tf.keras.layers.Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')( pool4) conv5 = tf.keras.layers.Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')( conv5) assert conv5.shape[1:] == (32, 32, 1024) drop5 = tf.keras.layers.Dropout(0.5)(conv5) # Expansive part up6 = tf.keras.layers.Conv2D(512, 2, activation='relu', padding='same', kernel_initializer='he_normal')( tf.keras.layers.UpSampling2D(size=(2, 2))(drop5)) assert up6.shape[1:] == (64, 64, 512) merge6 = tf.keras.layers.concatenate([drop4, up6], axis=3) assert merge6.shape[1:] == (64, 64, 1024) conv6 = tf.keras.layers.Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')( merge6) conv6 = tf.keras.layers.Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv6) assert conv6.shape[1:] == (64, 64, 512) up7 = tf.keras.layers.Conv2D(256, 2, activation='relu', padding='same', kernel_initializer='he_normal')( tf.keras.layers.UpSampling2D(size=(2, 2))(conv6)) assert up7.shape[1:] == (128, 128, 256) merge7 = tf.keras.layers.concatenate([conv3, up7], axis=3) assert merge7.shape[1:] == (128, 128, 512) conv7 = tf.keras.layers.Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')( merge7) conv7 = tf.keras.layers.Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv7) assert conv7.shape[1:] == (128, 128, 256) up8 = tf.keras.layers.Conv2D(128, 2, activation='relu', padding='same', kernel_initializer='he_normal')( tf.keras.layers.UpSampling2D(size=(2, 2))(conv7)) assert up8.shape[1:] == (256, 256, 128) merge8 = tf.keras.layers.concatenate([conv2, up8], axis=3) assert merge8.shape[1:] == (256, 256, 256) conv8 = tf.keras.layers.Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')( merge8) conv8 = tf.keras.layers.Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv8) assert conv8.shape[1:] == (256, 256, 128) up9 = tf.keras.layers.Conv2D(64, 2, activation='relu', padding='same', kernel_initializer='he_normal')( tf.keras.layers.UpSampling2D(size=(2, 2))(conv8)) assert up9.shape[1:] == (512, 512, 64) merge9 = tf.keras.layers.concatenate([conv1, up9], axis=3) assert merge9.shape[1:] == (512, 512, 128) conv9 = tf.keras.layers.Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge9) conv9 = tf.keras.layers.Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9) assert conv9.shape[1:] == (512, 512, 64) conv9 = tf.keras.layers.Conv2D(2, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9) assert conv9.shape[1:] == (512, 512, 2) conv10 = tf.keras.layers.Conv2D(num_classes, 1, activation='sigmoid')(conv9) assert conv10.shape[1:] == (512, 512, num_classes) model = tf.keras.Model(inputs=inputs, outputs=conv10)
其中conv1~10是上文图中的卷积层的卷积的定义。pool序列是池化层的定义。
我使用的isbi_2012数据集有如下的结构:
分别为测试集,训练集,训练集的标签。
用如下代码开始训练。
def make_train_generator(batch_size, aug_dict): image_gen = ImageDataGenerator(**aug_dict) mask_gen = ImageDataGenerator(**aug_dict) # set image and mask same augmentation using same seed image_generator = image_gen.flow_from_directory( directory='./isbi_2012/preprocessed', classes=['train_imgs'], class_mode=None, target_size=(512, 512), batch_size=batch_size, color_mode='grayscale', seed=1 ) mask_generator = mask_gen.flow_from_directory( directory='./isbi_2012/preprocessed', classes=['train_labels'], class_mode=None, target_size=(512, 512), batch_size=batch_size, color_mode='grayscale', seed=1 ) train_generator = zip(image_generator, mask_generator) for (batch_images, batch_labels) in train_generator: batch_images, batch_labels = normolize(batch_images, batch_labels) yield (batch_images, batch_labels)
这段代码给出了训练集和训练集标签的读取,定义了目标文件的大小(512*512)和读取方式(灰度图),由于算法中需要采用随机数,两者都用同样的随机种子(seed=1)。
经过了1小时的训练,运行结果如下图:
随着周期数的推进,训练准确度也越来越高。但是我的计算资源有限,论文中训练了10个周期,每个周期2000轮,准确率会非常高。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。