Keras多GPU训练问题解决汇总-多GPU-速度慢-单GPU无法加载权重文件-pretrain问题解决

1 篇文章 0 订阅
1 篇文章 0 订阅

我使用的是qqwweee/keras-yolo3的代码,代码地址

目前用于做某个设备的工业化检测,实现召回率98%以上,误检在2%左右,已满足应用需求。

解决了4个问题:

1.多GPU训练问题;

2.多GPU训练速度慢的问题;

3.单GPU无法加载多GPU跑出的加载权重问题;

4.Pretrain训练收敛速度慢的问题。


1.多GPU训练问题

参考的是这篇文章

①首先在train.py的def _main()里,加上parallel_model = multi_gpu_model(model, gpus=4),因为我的GPU为4个,所以gpus=4

def _main():
    annotation_path = 'label-29669.txt'
    log_dir = 'logs/000/'
    classes_path = 'model_data/0classes4.txt'
    anchors_path = 'model_data/yolo_anchors.txt'
    class_names = get_classes(classes_path)
    num_classes = len(class_names)
    anchors = get_anchors(anchors_path)

    input_shape = (640, 640)  # multiple of 32, hw

    model = create_model(input_shape, anchors, num_classes,
        freeze_body=2, weights_path='model_data/train.h5')  

    parallel_model = multi_gpu_model(model, gpus=4)

然后把里面所有的model.compile、model.fit_generator都改成parallel_model.compile、parallel_model.generator,共改4处。

②qqwweee/keras-yolo3模型默认采用的是一块GPU,在直接使用model = multi_gpu_model(model,gpus=N)时,模型会报错tensorflow.python.framework.errors_impl.InvalidArgumentError: Can’t concatenate scalars (use tf.stack instead) for ‘yolo_loss_1/concat’ (op: ‘ConcatV2’) with input shapes: [], [], [], [].,这是因为该模型的设计的loss输出时一个标量,需要多输出进行修改,才能实现并行。具体修改方式如下:
yolo_loss函数中的
xy_loss = K.sum(xy_loss) / mf
wh_loss = K.sum(wh_loss) / mf
confidence_loss = K.sum(confidence_loss) / mf
class_loss = K.sum(class_loss) / mf
loss += xy_loss + wh_loss + confidence_loss +class_loss
替换成下面的代码
xy_loss = K.sum(K.sum(xy_loss,axis=[2,3,4]),1,keepdims=True)
wh_loss = K.sum(K.sum(wh_loss,axis=[2,3,4]),1,keepdims=True)
confidence_loss = K.sum(K.sum(confidence_loss,axis=[2,3,4]),1,keepdims=True)
class_loss = K.sum(K.sum(class_loss,axis=[2,3,4]),1,keepdims=True)
loss += xy_loss + wh_loss + confidence_loss +class_loss
即实现对loss的改进,保证输入N张图片,输出时(N,1)对应的时N张图片的loss

这个函数在yolo3文件夹model.py里。

③然后将编译语句model.compile(optimizer=Adam(lr=1e-3), loss={ ‘yolo_loss’: lambda y_true, y_pred: y_pred})改为
model.compile(optimizer=Adam(lr=1e-3), loss=totalloss),其中totalloss为下面定义的损失函数,下面这个定义要添加进train.py里。
def totalloss(y_true, y_pred):
return K.sum(y_pred)/K.cast(K.shape(y_pred)[0],K.dtype(y_pred))

在train.py里添加和修改,记住两个if True的model.compile都要改loss。


④再将model_loss = Lambda(yolo_loss, output_shape=(1,),name=‘yolo_loss’,arguments={‘anchors’: anchors, ‘num_classes’: num_classes, ‘ignore_thresh’: 0.5})( [*model_body.output, *y_true])中的, output_shape=(1,)去掉,

直接替换为model_loss = Lambda(yolo_loss,name=‘yolo_loss’,arguments={‘anchors’: anchors, ‘num_classes’: num_classes, ‘ignore_thresh’: 0.5})( [*model_body.output, *y_true])

model_loss在train.py的def create_model函数定义里。


修改完这4处之后,就直接可以进行多gpu训练了。

2.多GPU训练速度慢的问题
多GPU跑起来后,发现训练速度并没有变快。在代码中打了几个时间标签后发现yolo模块处理时间只占一个batch处理时间的10%左右,其余时间花在图像预处理上。图像预处理在train.py的data_generator函数中,其中:

image, box = get_random_data(annotation_lines[i], input_shape, random=True)

get_random_data在yolo3文件夹的utils.py中定义。如果random=True的话,要搞平移、翻转、扭曲一堆图像增强,还需要标定框跟随。如果random=False,也需要把原图转换成预先定义的(416,416)size,时间都费在这上面了!

解决这个问题的方法是:如果需要图像增强,事先人工把图像增强一下,存到数据集里。然后random定义为false,同时自己编个码,把图像size都归一化到预先定义的size里,保存到数据集。这样的好处是,如果有200个epoch,每张图片都需要resize200次,事先做好,训练时一次也不需要,节约大量训练时间。

对get_random_data进行改写如下:

def get_random_data(annotation_line, input_shape, random=True, max_boxes=20, jitter=.3, hue=.1, sat=1.5, val=1.5, proc_img=True):
    '''random preprocessing for real-time data augmentation'''
    line = annotation_line.split()
    image = Image.open(line[0])
    box = np.array([np.array(list(map(int, box.split(',')))) for box in line[1:]])
    if not random:
        image_data = np.array(image) / 255.
        box_data = np.zeros((max_boxes, 5))
        box_data[:len(box)] = box
        return image_data, box_data

简单粗暴!图像处理时间大大压缩,加速效果也很明显,4个GPU都在满负荷工作了。

3.单GPU无法加载多GPU跑出的加载权重问题

参考文章

多GPU训练是通过multi_gpu_model把model分配到多块GPU上并行运算,所以训练出来的中间模型也只适用于训练它的机器。因为保存的方法是pararell_model.save,来看代码:

parallel_model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
            steps_per_epoch=max(1, num_train//batch_size),
            validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
            validation_steps=max(1, num_val//batch_size),
            epochs=500,
            initial_epoch=100,
            callbacks=[logging, checkpoint, reduce_lr]) 

中间权重是通过回调checkpoint来保存的,checkpoint是ModelCheckpoint的返回值,对ModelCheckpoint类进行改写,用单GPU模型来保存权重文件,在callbacks.py里。

    def __init__(self, filepath, monitor='val_loss', verbose=0,
                 save_best_only=False, save_weights_only=False,
                 mode='auto', period=1, save_model=0):     # 增加save_mode参数,调用时用单GPU模型
        super(ModelCheckpoint, self).__init__()
        self.monitor = monitor
        self.verbose = verbose
        self.filepath = filepath
        self.save_best_only = save_best_only
        self.save_weights_only = save_weights_only
        self.period = period
        self.epochs_since_last_save = 0
        self.save_model = save_model       # 单GPU参数
        if mode not in ['auto', 'min', 'max']:
            warnings.warn('ModelCheckpoint mode %s is unknown, '
                          'fallback to auto mode.' % (mode),
                          RuntimeWarning)
            mode = 'auto'

        if mode == 'min':
            self.monitor_op = np.less
            self.best = np.Inf
        elif mode == 'max':
            self.monitor_op = np.greater
            self.best = -np.Inf
        else:
            if 'acc' in self.monitor or self.monitor.startswith('fmeasure'):
                self.monitor_op = np.greater
                self.best = -np.Inf
            else:
                self.monitor_op = np.less
                self.best = np.Inf

    def on_epoch_end(self, epoch, logs=None):
        logs = logs or {}
        self.epochs_since_last_save += 1
        if self.epochs_since_last_save >= self.period:
            self.epochs_since_last_save = 0
            filepath = self.filepath.format(epoch=epoch + 1, **logs)
            if self.save_best_only:
                current = logs.get(self.monitor)
                if current is None:
                    warnings.warn('Can save best model only with %s available, '
                                  'skipping.' % (self.monitor), RuntimeWarning)
                else:
                    if self.monitor_op(current, self.best):
                        if self.verbose > 0:
                            print('\nEpoch %05d: %s improved from %0.5f to %0.5f,'
                                  ' saving model to %s'
                                  % (epoch + 1, self.monitor, self.best,
                                     current, filepath))
                        self.best = current
                        if self.save_weights_only:
                            # self.model.save_weights(filepath, overwrite=True)
                            self.save_model.save_weights(filepath)  # 单GPU save model
                        else:
                            self.model.save(filepath, overwrite=True)
                    else:
                        if self.verbose > 0:
                            print('\nEpoch %05d: %s did not improve from %0.5f' %
                                  (epoch + 1, self.monitor, self.best))
            else:
                if self.verbose > 0:
                    print('\nEpoch %05d: saving model to %s' % (epoch + 1, filepath))
                if self.save_weights_only:
                    # self.model.save_weights(filepath, overwrite=True)
                    self.save_model.save_weights(filepath)  # 单GPU save model
                else:
                    self.model.save(filepath, overwrite=True)

保存的中间模型就可以用于单GPU机器加载了。

4.Pretrain训练收敛速度慢的问题

如果用预训练模型,在def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
            weights_path='model_data/train.h5')定义中,如果load_pretrained=True,它认为你只需要训练最后2层全连接分类网络,前面的卷积神经网络就freeze了,所以训练速度快,收敛速度就慢了。如果要全网络训练,需屏蔽定义中的以下代码:

    if load_pretrained:
        model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
        print('Load weights {}.'.format(weights_path))

        # 如果加载预训练模型后还要训练卷积神经网络的话,需屏蔽以下代码
        '''
        if freeze_body in [1, 2]:
            # Freeze darknet53 body or freeze all but 3 output layers.
            num = (185, len(model_body.layers)-3)[freeze_body-1]
            for i in range(num): model_body.layers[i].trainable = False
            print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers)))
        '''

问题解决!

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值