Unet项目解析(2):./src/retinaNN_training.py
项目GitHub主页:https://github.com/orobix/retina-unet
参考论文:Retina blood vessel segmentation with a convolution neural network (U-net) Retina blood vessel segmentation with a convolution neural network (U-net)
1. 导入依赖的模块 以及 依赖的脚本
1.1 导入依赖的模块
import numpy as np
import configparser
from keras.models import Model
from keras.layers import Input, concatenate, Conv2D, MaxPooling2D, UpSampling2D, Reshape, core, Dropout
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint, LearningRateScheduler
from keras import backend as K
from keras.utils.vis_utils import plot_model as plot
from keras.optimizers import SGD
- keras.layers.core
core内部定义了一系列常用的网络层,包括全连接、**层等
- keras.callbacks.ModelCheckpoint(filepath,monitor='val_loss', verbose=0, save_best_only=False, save_weights_only = False, mode='auto', period=1)
该回调函数将在每个epoch后保存模型到filepath;
filename:字符串,保存模型的路径
monitor:需要监视的值
verbose:信息展示模式,0或1
save_best_only:当设置为True时,将只保存在验证集上性能最好的模型
mode:‘auto’,‘min’,‘max’之一,在save_best_only=True时决定性能最佳模型的评判准则,例如,当监测值为val_acc时,模式应为max,当检测值为val_loss时,模式应为min。在auto模式下,评价准则由被监测值的名字自动推断。
save_weights_only:若设置为True,则只保存模型权重,否则将保存整个模型(包括模型结构,配置信息等)
period:CheckPoint之间的间隔的epoch数
- keras.callbacks.LearningRateScheduler(schedule)
该回调函数是学习率调度器
schedule:函数,该函数以epoch号为参数(从0算起的整数),返回一个新学习率(浮点数)
# def step_decay(epoch):
# lrate = 0.01 #the initial learning rate (by default in keras)
# if epoch==100:
# return 0.005
# else:
# return lrate
#
# lrate_drop = LearningRateScheduler(step_decay)
1.2 导入依赖的脚本文件
import sys
sys.path.insert(0, '/home/shenziheng/SpyderProject/Retina_NN/lib') # 加载指向脚本文件目录
from help_functions import * # 导入help_functions脚本文件中的所有函数
from extract_patches import get_data_training # 导入extract_patches 脚本中的 get_data_training函数
2.构建Unet网络
def get_unet(n_ch,patch_height,patch_width):
inputs = Input(shape=(n_ch,patch_height,patch_width))
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same',data_format='channels_first')(inputs)
conv1 = Dropout(0.2)(conv1)
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same',data_format='channels_first')(conv1)
pool1 = MaxPooling2D((2, 2))(conv1)
#
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same',data_format='channels_first')(pool1)
conv2 = Dropout(0.2)(conv2)
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same',data_format='channels_first')(conv2)
pool2 = MaxPooling2D((2, 2))(conv2)
#
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same',data_format='channels_first')(pool2)
conv3 = Dropout(0.2)(conv3)
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same',data_format='channels_first')(conv3)
up1 = UpSampling2D(size=(2, 2))(conv3)
up1 = concatenate([conv2,up1],axis=1)
conv4 = Conv2D(64, (3, 3), activation='relu', padding='same',data_format='channels_first')(up1)
conv4 = Dropout(0.2)(conv4)
conv4 = Conv2D(64, (3, 3), activation='relu', padding='same',data_format='channels_first')(conv4)
#
up2 = UpSampling2D(size=(2, 2))(conv4)
up2 = concatenate([conv1,up2], axis=1)
conv5 = Conv2D(32, (3, 3), activation='relu', padding='same',data_format='channels_first')(up2)
conv5 = Dropout(0.2)(conv5)
conv5 = Conv2D(32, (3, 3), activation='relu', padding='same',data_format='channels_first')(conv5)
#
conv6 = Conv2D(2, (1, 1), activation='relu',padding='same',data_format='channels_first')(conv5)
conv6 = core.Reshape((2,patch_height*patch_width))(conv6)
conv6 = core.Permute((2,1))(conv6)
############
conv7 = core.Activation('softmax')(conv6)
model = Model(inputs=inputs, outputs=conv7)
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.3, nesterov=False)
model.compile(optimizer='sgd', loss='categorical_crossentropy',metrics=['accuracy'])
return model
- 使用Model来初始化一个函数式模型
from keras.models import Model
from keras.layers import Input, Dense
a = Input(shape=(32,))
b = Dense(32)(a)
model = Model(inputs=a, outputs=b)
在这里,我们的模型以a为输入,以b为输出,同样我们可以构造拥有多输入和多输出的模型model = Model(inputs=[a1, a2], outputs=[b1, b3, b3])
- 模型Model的方法compile
本函数编译模型以供训练,参数有
optimizer:优化器,为预定义优化器名或优化器对
loss:损失函数,为预定义损失函数名或一个目标函数
metrics:列表,包含评估模型在训练和测试时的性能的指标,典型用法是metrics=['accuracy']如果要在多输出模型中为不同的输出指定不同的指标,可像该参数传递一个字典,例如metrics={'ouput_a': 'accuracy'}
sample_weight_mode:如果需要按时间步为样本赋权(2D权矩阵),将该值设为“temporal”。默认为“None”,代表按样本赋权(1D权)。如果模型有多个输出,可以向该参数传入指定sample_weight_mode的字典或列表。在下面fit函数的解释中有相关的参考内容。
weighted_metrics: metrics列表,在训练和测试过程中,这些metrics将由sample_weight或clss_weight计算并赋权
target_tensors: 默认情况下,Keras将为模型的目标创建一个占位符,该占位符在训练过程中将被目标数据代替。如果你想使用自己的目标张量(相应的,Keras将不会在训练时期望为这些目标张量载入外部的numpy数据),你可以通过该参数手动指定。目标张量可以是一个单独的张量(对应于单输出模型),也可以是一个张量列表,或者一个name->tensor的张量字典。
kwargs:使用TensorFlow作为后端请忽略该参数,若使用Theano/CNTK作为后端,kwargs的值将会传递给 K.function。如果使用TensorFlow为后端,这里的值会被传给tf.Session.run
在Keras中,compile主要完成损失函数和优化器的一些配置,是为训练服务的。
- 优化器optimizers
可以在调用model.compile()之前初始化一个优化器对象,然后传入该函数。
- 用于维度交换的permute层
Permute层将输入的维度按照给定模式进行重排,例如,当需要将RNN和CNN网络连接时,可能会用到该层。
dims:整数tuple,指定重排的模式,不包含样本数的维度。重排模式的下标从1开始。例如(2,1)代表将输入的第二个维度重拍到输出的第一个维度,而将输入的第一个维度重排到第二个维度。这一点和TensorFlow很不一样。
model.add(Permute((2, 1), input_shape=(10, 64)))
# now: model.output_shape == (None, 64, 10)
# note: `None` is the batch dimension
- Conv2D / MaxPooling2D / UpSampling2D 需要指定通道的位置
keras.layers.convolutional.Conv2D(filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)
keras.layers.pooling.MaxPooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None)
keras.layers.convolutional.UpSampling2D(size=(2, 2), data_format=None)
data_format:字符串,“channels_first”或“channels_last”之一,代表图像的通道维的位置。该参数是Keras 1.x中的image_dim_ordering,“channels_last”对应原本的“tf”,“channels_first”对应原本的“th”。以128x128的RGB图像为例,“channels_first”应将数据组织为(3,128,128),而“channels_last”应将数据组织为(128,128,3)。该参数的默认值是~/.keras/keras.json中设置的值,若从未设置过,则为“channels_last”。
-
1×1的卷积的作用
大概有两个方面的作用:1. 实现跨通道的交互和信息整合2. 进行卷积核通道数的降维和升维。
3.加载配置文件中的训练参数和训练数据
#========= Load settings from Config file
config = configparser.RawConfigParser()
config.read('configuration.txt')
#patch to the datasets
path_data = config.get('data paths', 'path_local') #数据文件封装后的文件路径
#Experiment name
name_experiment = config.get('experiment name', 'name')
#training settings
N_epochs = int(config.get('training settings', 'N_epochs')) #迭代的次数
batch_size = int(config.get('training settings', 'batch_size')) #训练的批量大小
patches_imgs_train, patches_masks_train = get_data_training(
DRIVE_train_imgs_original = path_data + config.get('data paths', 'train_imgs_original'),
DRIVE_train_groudTruth = path_data + config.get('data paths', 'train_groundTruth'), #masks
patch_height = int(config.get('data attributes', 'patch_height')),
patch_width = int(config.get('data attributes', 'patch_width')),
N_subimgs = int(config.get('training settings', 'N_subimgs')),
inside_FOV = config.getboolean('training settings', 'inside_FOV') #select the patches only inside the FOV (default == True)
)
显示示例数据:
#========= Save a sample of what you're feeding to the neural network ==========
N_sample = min(patches_imgs_train.shape[0],40)
visualize(group_images(patches_imgs_train[0:N_sample,:,:,:],5),'./'+name_experiment+'/'+"sample_input_imgs").show()
visualize(group_images(patches_masks_train[0:N_sample,:,:,:],5),'./'+name_experiment+'/'+"sample_input_masks").show()
4. 调用网络 及 保存网络模型
#=========== Construct and save the model arcitecture =====
n_ch = patches_imgs_train.shape[1]
patch_height = patches_imgs_train.shape[2]
patch_width = patches_imgs_train.shape[3]
#U-net 网络 [batchsize, channels, patch_heigh, patch_width]
model = get_unet(n_ch, patch_height, patch_width)
print ("Check: final output of the network:")
print (model.output_shape)
#调用pydot显示模型
plot(model, to_file='./'+name_experiment+'/'+name_experiment + '_model.png')
#保存模型
json_string = model.to_json()
with open('./'+name_experiment+'/'+name_experiment +'_architecture.json', 'w') as jsonfile:
jasonfile.write(json_string)
5. 训练阶段
5.1 动态调整学习率并实时保存each epoch的checkpoint数据
#采用回调函数的形式保存每个epoch数据
checkpointer = ModelCheckpoint(
filepath='./'+name_experiment+'/'+name_experiment +'_best_weights.h5',
verbose=1, monitor='val_loss', mode='auto', save_best_only=True)
#采用回调函数的形式动态设置学习率
def step_decay(epoch):
lrate = 0.01
if epoch % 1 is 0:
return lrate - epoch*0.001
else:
return lrate
lrate_drop = LearningRateScheduler(step_decay)
- 回调函数ModelCheckpoint()
keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', verbose=0,
save_best_only=False, save_weights_only=False, mode='auto', period=1)
该回调函数将在每个epoch后保存模型到filepath。filepath可以是格式化的字符串,里面的占位符将会被epoch值和传入on_epoch_end的logs关键字所填入;filename:字符串,保存模型的路径
monitor:需要监视的值
verbose:信息展示模式,0或1
save_best_only:当设置为True时,将只保存在验证集上性能最好的模型
mode:‘auto’,‘min’,‘max’之一,在save_best_only=True时决定性能最佳模型的评判准则,例如,当监测值为val_acc时,模式应为max,当检测值为val_loss时,模式应为min。在auto模式下,评价准则由被监测值的名字自动推断。
save_weights_only:若设置为True,则只保存模型权重,否则将保存整个模型(包括模型结构,配置信息等)
period:CheckPoint之间的间隔的epoch数
- 回调函数LearningRateScheduler()
keras.callbacks.LearningRateScheduler(schedule)
该回调函数是学习率调度器。
schedule:自定义函数,该函数以epoch号为参数(从0算起的整数),返回一个新学习率(浮点数)
5.2 模型训练 model.fit
#模型训练
model.fit(patches_imgs_train, patches_masks_train, epoch=N_epochs, batch_size=batch_size,
verbose=2, shuffle=True, validation_split=0.1, callbacks=[checkpointer])
真正的训练函数,调用方法如下:
fit(self, x, y, batch_size=32, epochs=10, verbose=1, callbacks=None, validation_split=0.0,
validation_data=None, shuffle=True, class_weight=None, sample_weight=None, initial_epoch=0)
本函数将模型训练nb_epoch轮,其参数有:x:输入数据。如果模型只有一个输入,那么x的类型是numpy array,如果模型有多个输入,那么x的类型应当为list,list的元素是对应于各个输入的numpy array
y:标签,numpy array
batch_size:整数,指定进行梯度下降时每个batch包含的样本数。
epochs:整数,训练终止时的epoch值,训练将在达到该epoch值时停止
verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
callbacks:list,其中的元素是keras.callbacks.Callback的对象,如ModelCheckpoint()、LearningRateScheduler()等。
validation_split:0~1之间的浮点数,用来指定训练集的一定比例数据作为验证集。注意,validation_split的划分在shuffle之前,因此如果你的数据本身是有序的,需要先手工打乱再指定validation_split,否则可能会出现验证集样本不均匀。
validation_data:形式为(X,y)的tuple,是指定的验证集。此参数将覆盖validation_spilt。
shuffle:布尔值或字符串,一般为布尔值,表示是否在训练过程中随机打乱输入样本的顺序。若为字符串“batch”,则是用来处理HDF5数据的特殊情况,它将在batch内部将数据打乱。
class_weight:字典,将不同的类别映射为不同的权值,该参数用来在训练过程中调整损失函数(只能用于训练)
sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode='temporal'。
initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。
6.保存和测试
#========== Save and test the last model ===================
model.save_weights('./'+name_experiment+'/'+name_experiment +'_last_weights.h5', overwrite=True)
#test the model
score = model.evaluate(patches_imgs_test, masks_Unet(patches_masks_test), verbose=0)
print('Test score:', score[0])
print('Test accuracy:', score[1])
Keras中模型的保存分为两部分分别是保存架构jasonfile.write()和权重save_weights();同时模型的读取也包括网络架构读取model = model_from_json(open('').read()) 和模型训练好的权重读取model.load_weights('')。
具体如下所示:
json_string = model.to_json() #等价于 json_string = model.get_config()
open('my_model_architecture.json','w').write(json_string)
model.save_weights('my_model_weights.h5')
#加载模型数据和weights
model = model_from_json(open('my_model_architecture.json').read())
model.load_weights('my_model_weights.h5')
模型测试:
evaluate(self, x, y, batch_size=32, verbose=1, sample_weight=None)
本函数按batch计算在某些输入数据上模型的误差。x:输入数据,是numpy array或numpy array的list
y:标签,numpy array
batch_size:整数,含义同fit的同名参数
verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,只能取0或1
sample_weight:numpy array,含义同fit的同名参数
本函数返回一个测试误差的标量值(如果模型没有其他评价指标),或一个标量的list(如果模型还有其他的评价指标)。model.metrics_names将给出list中各个值的含义。推荐阅读
-
客户管理模块(文件上传-图片/删除-修改客户/条件查询客户)| CRM客户关系管理系统项目实战三(Struts2+Spring+Hibernate)解析+源代码
-
Unet项目解析(4): ./src/RetinaNN_predict.py
-
Unet项目解析(6): 图像分块、整合 / 数据对齐、网络输出转成图像
-
Unet项目解析(5): 数据封装、数据加载、数据显示
-
Unet项目解析(2):./src/retinaNN_training.py
-
vue2的todolist入门小项目的详细解析
-
yii2项目前台页面开发中,用到的算法1 例题解析
-
rails3项目解析之2——rails基础 Rails项目管理RubyRediscapistrano
-
Spring Boot微服务项目实战(第2版)学习笔记-第20章Spring Boot原理解析
-
客户管理模块(文件上传-图片/删除-修改客户/条件查询客户)| CRM客户关系管理系统项目实战三(Struts2+Spring+Hibernate)解析+源代码