본문 바로가기

Python 프로그래밍

파이썬 YOLO 텐서플로우 학습하기

이번장에서는 파이썬을 이용해서 yolo를 학습하는걸 할텐데 보통 다크넷을 통해 하면 텐서보드같은 유용한 툴을 못쓰므로 텐서플로우 를 이용해서 yolo를 학습하는걸 할것이다.

yolo가 먼지 모르는 분들은 전장에 설명해놓은거 참고하시기 바랍니다.

https://easytocoding.tistory.com/11

yolo가 다크넷을 기반으로 하기 때문에 학습할때 잘되는지 분석하려면 쉽지가 않다 그래서 이번에는 텐서플로우 를 통해 yolo 학습을 해서 tensorboard로 분석할수 있게 한다.

학습에 필요한 패키지는.

numpy>=1.18.2
scipy>=1.4.1
seaborn>=0.10.0
tensorflow=2.2.*
opencv-python==4.1.2.30
tqdm==4.43.0
pandas
awscli
urllib3

우선 텐서플로우 로 레이어를 모두 구현을 해야 되므로

yolo v3의 구조를 살펴보면 다크넷 컨볼루션 레이어자체가 텐서플로우의 컬볼루션 레이어에 Batch

Nomarization을 하고 그다음 Leaky ReLU를 더한거랑 같은 구조랑 같고

Residual 유닛은 다크넷 컨볼루션 레이어두개를 겹친걸 더한값과 같게 구현하면되고,

그렇게 구현한 Residual 유닛을 통해 Resdial 블록 을 구현하면 된다.

이걸 통해서 텐서플로우를 통해 yolov3을 구현하면 아래와 같이 된다.

def YOLOv3(input_layer, NUM_CLASS):
    route_1, route_2, conv = darknet53(input_layer)
    conv = convolutional(conv, (1, 1, 1024,  512))
    conv = convolutional(conv, (3, 3,  512, 1024))
    conv = convolutional(conv, (1, 1, 1024,  512))
    conv = convolutional(conv, (3, 3,  512, 1024))
    conv = convolutional(conv, (1, 1, 1024,  512))
    conv_lobj_branch = convolutional(conv, (3, 3, 512, 1024))
    
    conv_lbbox = convolutional(conv_lobj_branch, (1, 1, 1024, 3*(NUM_CLASS + 5)), activate=False, bn=False)

    conv = convolutional(conv, (1, 1,  512,  256))
   
    conv = upsample(conv)

    conv = tf.concat([conv, route_2], axis=-1)
    conv = convolutional(conv, (1, 1, 768, 256))
    conv = convolutional(conv, (3, 3, 256, 512))
    conv = convolutional(conv, (1, 1, 512, 256))
    conv = convolutional(conv, (3, 3, 256, 512))
    conv = convolutional(conv, (1, 1, 512, 256))
    conv_mobj_branch = convolutional(conv, (3, 3, 256, 512))

    conv_mbbox = convolutional(conv_mobj_branch, (1, 1, 512, 3*(NUM_CLASS + 5)), activate=False, bn=False)

    conv = convolutional(conv, (1, 1, 256, 128))
    conv = upsample(conv)

    conv = tf.concat([conv, route_1], axis=-1)
    conv = convolutional(conv, (1, 1, 384, 128))
    conv = convolutional(conv, (3, 3, 128, 256))
    conv = convolutional(conv, (1, 1, 256, 128))
    conv = convolutional(conv, (3, 3, 128, 256))
    conv = convolutional(conv, (1, 1, 256, 128))
    conv_sobj_branch = convolutional(conv, (3, 3, 128, 256))

    conv_sbbox = convolutional(conv_sobj_branch, (1, 1, 256, 3*(NUM_CLASS +5)), activate=False, bn=False)
        
    return [conv_sbbox, conv_mbbox, conv_lbbox]
 

Residual블록 설계 알고리즘을 short-cut 매커니즘으로 설계해서 신경망자체의 깊이를 증가함으로써 ID매핑을 통해 입력 레이어와 출력레이어간의 직접적으로 채널을 구현해서 residaul를 학습하는데 집중하도록 만듭니다.

코드로 구현하면

def residual_block(input_layer, input_channel, filter_num1, filter_num2): 
	short_cut = input_layer
	conv = convolutional(input_layer, filters_shape= (1, 1, input_channel, filter_num1)) 
	conv = convolutional(conv , filters_shape= (3, 3, filter_num1, filter_num2)) 
 	residual_output = short_cut + conv 
	return residual_output​

 

이때 다크넷 컨볼루션이 텐서플로우의 컨볼루션에 batch정규화를 하고 LeakyReLU를 더한것과 같으므로 그것도 구현해놓습니다.

def convolutional(input_layer, filters_shape, downsample=False, activate=True, bn=True):
    if downsample:
        input_layer = ZeroPadding2D(((1, 0), (1, 0)))(input_layer)
        padding = 'valid'
        strides = 2
    else:
        strides = 1
        padding = 'same'

    conv = Conv2D(filters=filters_shape[-1], kernel_size = filters_shape[0], strides=strides,
                  padding=padding, use_bias=not bn, kernel_regularizer=l2(0.0005),
                  kernel_initializer=tf.random_normal_initializer(stddev=0.01),
                  bias_initializer=tf.constant_initializer(0.))(input_layer)
    if bn:
        conv = BatchNormalization()(conv)
    if activate == True:
        conv = LeakyReLU(alpha=0.1)(conv)

    return conv
class BatchNormalization(BatchNormalization):
    def call(self, x, training=False):
        if not training:
            training = tf.constant(False)
        training = tf.logical_and(training, self.trainable)
        return super().call(x, training)

 

이제 학습할때 순서는

1. yolo 신경망 생성하고.

2. config파일 세팅된거 읽어오고

3. 이미지 경로 train으로 된거랑 test로 된거 읽어오고(라벨링 다 되어있는)

4. 거기에 이미지 파일의 라벨링 파일 x_min, y_min,x_max,y_max,class_id순으로 저장한거 읽어오고

5. 기존 가중치파일 있으면 읽어와서 학습하는 순서로 진행하면 된다.

train 코드

if TRAIN_TRANSFER:
        Darknet = Create_Yolo(input_size=YOLO_INPUT_SIZE)
        load_yolo_weights(Darknet, Darknet_weights) # use darknet weights

    yolo = Create_Yolo(input_size=YOLO_INPUT_SIZE, training=True, CLASSES=TRAIN_CLASSES)
    if TRAIN_FROM_CHECKPOINT:
        try:
            yolo.load_weights(TRAIN_FROM_CHECKPOINT)ㅅ
        except ValueError:
            print("Shapes are incompatible, transfering Darknet weights")
            TRAIN_FROM_CHECKPOINT = False

    if TRAIN_TRANSFER and not TRAIN_FROM_CHECKPOINT:
        for i, l in enumerate(Darknet.layers):
            layer_weights = l.get_weights()
            if layer_weights != []:
                try:
                    yolo.layers[i].set_weights(layer_weights)
                except:
                    print("skipping", yolo.layers[i].name)
    
for epoch in range(TRAIN_EPOCHS):
    for image_data, target in trainset:
        results = train_step(image_data, target)
        cur_step = results[0]%steps_per_epoch
        print("epoch:{:2.0f} step:{:5.0f}/{}, lr:{:.6f}, giou_loss:{:7.2f}, conf_loss:{:7.2f}, prob_loss:{:7.2f}, total_loss:{:7.2f}"
                .format(epoch, cur_step, steps_per_epoch, results[1], results[2], results[3], results[4], results[5]))

    if len(testset) == 0:
        print("configure TEST options to validate model")
        yolo.save_weights(os.path.join(TRAIN_CHECKPOINTS_FOLDER, TRAIN_MODEL_NAME))
        continue
    
    count, giou_val, conf_val, prob_val, total_val = 0., 0, 0, 0, 0
    for image_data, target in testset:
        results = validate_step(image_data, target)
        count += 1
        giou_val += results[0]
        conf_val += results[1]
        prob_val += results[2]
        total_val += results[3]
    # writing validate summary data
    with validate_writer.as_default():
        tf.summary.scalar("validate_loss/total_val", total_val/count, step=epoch)
        tf.summary.scalar("validate_loss/giou_val", giou_val/count, step=epoch)
        tf.summary.scalar("validate_loss/conf_val", conf_val/count, step=epoch)
        tf.summary.scalar("validate_loss/prob_val", prob_val/count, step=epoch)
    validate_writer.flush()
        
    print("\n\ngiou_val_loss:{:7.2f}, conf_val_loss:{:7.2f}, prob_val_loss:{:7.2f}, total_val_loss:{:7.2f}\n\n".
            format(giou_val/count, conf_val/count, prob_val/count, total_val/count))

    if TRAIN_SAVE_CHECKPOINT and not TRAIN_SAVE_BEST_ONLY:
        save_directory = os.path.join(TRAIN_CHECKPOINTS_FOLDER, TRAIN_MODEL_NAME+"_val_loss_{:7.2f}".format(total_val/count))
        yolo.save_weights(save_directory)
    if TRAIN_SAVE_BEST_ONLY and best_val_loss>total_val/count:
        save_directory = os.path.join(TRAIN_CHECKPOINTS_FOLDER, TRAIN_MODEL_NAME)
        yolo.save_weights(save_directory)
        best_val_loss = total_val/count
    if not TRAIN_SAVE_BEST_ONLY and not TRAIN_SAVE_CHECKPOINT:
        save_directory = os.path.join(TRAIN_CHECKPOINTS_FOLDER, TRAIN_MODEL_NAME)
        yolo.save_weights(save_directory)

# measure mAP of trained custom model
mAP_model.load_weights(save_directory) # use keras weights

위와 같이 진행하면 이제 학습이 시작될것이다.

나중에 tensorboard --logdir=log해놓으면

웹브라우져에서 tensorboard를 볼수있다

이게 실제로 적용되면

위와 같은 식으로 실시간으로 검증이 된다.

잠시 텐서보드하나 사용하려고 왜 이렇게 하냐고 의문을 가지는 분들이 있을수 있는데

텐세보드의 강력한 기능인 실시간 이미지화로 값을 분석도 할수있고 실제로 코드로 구현하려면 그려야 할 그래프들이 너무 많기 때문이다.

 

 

반응형