Train U-Net from the beginning

教你如何使用网站API将IP地址转化成具体国家、城市或经纬度!

数据准备

我们所采用的数据是人的胚胎卵细胞的数据,下图中展示的就是我们所用到的胚胎图像的样例。从图中可以看到的是,一个是图像的噪声比较大,另一个是受到曝光不均匀的影响,左侧光圈内部的图像可能无法正常被识别。

所以我们首先要解决光照不均匀的问题。这个问题的解决方法网上有很多,我们这里采用了一种比较简单的方式。首先我们将图像用最小值滤波产生一个背景板,随后对背景板进行均值滤波,然后让原始图像减去背景板,得到了下图。

可以看到的是我们的光照不均匀的问题已经基本解决了,还顺带解决了一部分的噪声问题。
随后我们对图片进行了打标。我们标注了370张左右的图片,为了避免过拟合,选取了其中的70张作为验证集,剩余的图片用于训练。

模型搭建

我们采用keras搭建了我们的模型,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def unet(pretrained_weights=None, input_size=(224, 224, 1)):
inputs = Input(input_size)
conv1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(inputs)
conv1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)

conv2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool1)
conv2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)

conv3 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool2)
conv3 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv3)
pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)

conv4 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool3)
conv4 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv4)
drop4 = Dropout(0.5)(conv4)
pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)

conv5 = Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool4)
conv5 = Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv5)
drop5 = Dropout(0.5)(conv5)

up6 = Conv2D(512, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
UpSampling2D(size=(2, 2))(drop5)
)
merge6 = concatenate([drop4, up6], axis=3)
conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge6)
conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv6)

up7 = Conv2D(256, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
UpSampling2D(size=(2, 2))(conv6))
merge7 = concatenate([conv3, up7], axis=3)
conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge7)
conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv7)

up8 = Conv2D(128, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
UpSampling2D(size=(2, 2))(conv7))
merge8 = concatenate([conv2, up8], axis=3)
conv8 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge8)
conv8 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv8)

up9 = Conv2D(64, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
UpSampling2D(size=(2, 2))(conv8))
merge9 = concatenate([conv1, up9], axis=3)
conv9 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge9)
conv9 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9)
conv9 = Conv2D(2, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9)
conv10 = Conv2D(1, 1, activation='sigmoid')(conv9)

model = Model(input=inputs, output=conv10)
model.compile(optimizer=Adam(lr=1e-4), loss='binary_crossentropy', metrics=['accuracy'])

if (pretrained_weights):
model.load_weights(pretrained_weights)

return model

数据增强

数据增强部分,我们采用了keras自带的image preprocessing。要保证的是,img_sub_folder和mask_sub_folder在root的文件下,图片都采用从0开始的数字序列命名,而且要保证相同名字的图片和mask对应。
在adjustData中,只做了将图片除以255的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import numpy as np
import os
from keras.preprocessing.image import ImageDataGenerator
import cv2
def get_train_generator(root, img_sub_folder,
mask_sub_folder, aug_dict,
target_size=224, batch_size=5,
save_to_dir=False, img_save_prefix='image',
mask_save_prefix='mask',
seed=1007):
image_datagen = ImageDataGenerator(**aug_dict)
mask_datagen = ImageDataGenerator(**aug_dict)
image_generator = image_datagen.flow_from_directory(
root,
classes=[img_sub_folder],
class_mode=None,
color_mode='grayscale',
target_size=(target_size, target_size),
batch_size=batch_size,
save_to_dir=save_to_dir,
save_prefix=img_save_prefix,
seed=seed
)
mask_generator = mask_datagen.flow_from_directory(
root,
classes=[mask_sub_folder],
class_mode=None,
color_mode='grayscale',
target_size=(target_size, target_size),
batch_size=batch_size,
save_to_dir=save_to_dir,
save_prefix=mask_save_prefix,
seed=seed
)
train_generator = zip(image_generator, mask_generator)
for (img, mask) in train_generator:
img, mask = adjustData(img, mask)
yield (img, mask)


def adjustData(img, mask):
# mask = mask[:, :, :, 0] if (len(mask.shape) == 4) else mask[:, :, 0]
mask = mask[:, :, :, 0]
mask = mask[:, :, :, np.newaxis]
mask /= 255.
img /= 255.
mask = np.where(mask <= 0.5, 0, 1)
# print('percentage: {}'.format(np.sum(mask.flatten() == 1)/np.sum(mask.flatten() == 0)))
return (img, mask)

模型训练

为了避免过拟合,我们用验证集进行了早停。patience设置为5。

1
2
3
4
model = unet()
checkpoint = ModelCheckpoint('checkpoints/unet_duco.h5', monitor='val_loss', verbose=1, save_best_only=True)
early = EarlyStopping(patience=5, verbose=1)
model.fit_generator(generator, steps_per_epoch=20, epochs=100, callbacks=[checkpoint], validation_data=(x_val, y_val))

训练结果

验证集正确率达到了98.65%,训练集正确率达到了98.53%。