使用深度学习框架darknet训练模型

训练数据生成

详细步骤参考

配置文件的修改

本文以人手检测为例配置(只有一个label:hand)

  1. 添加或修改data/hand.names文件,此文件记录label,每行一个label(注意:对应于训练数据的labels)
  2. 添加或修改cfg/hand.data文件,其内容如下:
classes= 1  # 自己数据集的类别数(不包含背景类)
train  = /home/xxx/darknet/train.txt  # train文件的路径
valid  = /home/xxx/darknet/test.txt   # test文件的路径
names = /home/xxx/darknet/data/hand.names
backup = /home/xxx/darknet/backup   # 生成权重存放文件夹,如果不存在,需提前创建
  1. 修改cfg/yolov3.cfg文件
    在这里插入图片描述
    如图所示修改3处,直接根据“yolo”关键字查询定位,然后根据注释修改:
    在这里插入图片描述
    anchors的计算采用kmeans算法进行聚类而获得,计算代码如下:
#coding=utf-8
import numpy as np
import os
from operator import attrgetter

#bbox类
class Box():
    def __init__(self,x,y,w,h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def __repr__(self):
        return repr((self.x,self.y,self.w,self.h))


#计算两个box交集面积
#box1和box2是Box类的两个对象
#返回两个box交集的面积
def cal_box_intersection(box1,box2):
    overlap_width = min(box1.w,box2.w)
    overlap_height = min(box1.h,box2.h)
    if overlap_width < 0 or overlap_height < 0:
        return 0
    return overlap_height * overlap_width

#计算两个box并集面积
#box1和box2是Box类的两个对象
#返回两个box并集的面积
def cal_box_union(box1,box2):
    box_intersection = cal_box_intersection(box1,box2)
    area_union = box1.w * box1.h + box2.w * box2.h - box_intersection
    return area_union

#计算两个box的iou
#box1和box2是Box类的两个对象
#返回两个box的iou值
def cal_iou(box1,box2):
    iou = cal_box_intersection(box1,box2) / cal_box_union(box1,box2)
    return iou

#k-mean++算法中用于初始化种子点
#boxes为装载Box类实例的列表
#num_anchor为需要种子点数量,即需要聚类的数量
def init_centroids(boxes,num_anchor):
    centroids = []
    num_box = len(boxes)

    centroid_index = np.random.choice(num_box,1)
    centroids.append(boxes[centroid_index[0]])

    for centroid_index in range(0,num_anchor -1):
        sum_dis = 0
        dis_threshold = 0
        dis_list = []
        cur_sum = 0

        for box in boxes:
            min_dis = 1
            for centroid_id, centroid_box in enumerate(centroids):
                dis = (1 - cal_iou(box,centroid_box))
                if dis < min_dis:
                    min_dis = dis
            sum_dis += min_dis
            dis_list.append(min_dis)

        dis_threshold = sum_dis * np.random.random()

        for i in range(0,num_box):
            cur_sum += dis_list[i]
            if(cur_sum > dis_threshold):
                centroids.append(boxes[i])
                break
    return centroids

#迭代一次kmeans算法
#num_anchor是种子点数目
#boxes是Box类实例的列表
#centroids是种子点列表
#返回新的种子点列表,box的分组和误差值
def do_kmeans(num_anchor,boxes,centroids):
    loss = 0
    groups = []
    new_centroids = []

    for i in range(num_anchor):
        groups.append([])
        new_centroids.append(Box(0,0,0,0))

    for box in boxes:
        min_dis = 1
        group_index = 0
        for centroid_id,centroid_box in enumerate(centroids):
            dis = 1 - cal_iou(box,centroid_box)
            if dis < min_dis:
                min_dis = dis
                group_index = centroid_id
        groups[group_index].append(box)
        loss += min_dis
        new_centroids[group_index].w += box.w
        new_centroids[group_index].h += box.h

    for i in range(num_anchor):
        len_group = len(groups[i])
        if len_group == 0:
            len_group += 1
        new_centroids[i].w /= len_group
        new_centroids[i].h /= len_group

    return new_centroids,groups,loss

#对得到的anchors按面积从小到大进行排序
def sort_centroids(centroids):
    for centroid in centroids:
        centroid.x = centroid.w * centroid.h
    sorted_centroids = sorted(centroids,key=attrgetter('x'))
    for centroid in sorted_centroids:
        centroid.x = 0
    return sorted_centroids

#计算得到样本的anchor值
#label_path为darknet的txt标注文件件路径
#num_anchor为需要生成的anchor的数量
#img_size为darknet的cfg中图像的尺寸
#num_iterations为最大的迭代次数,超过则停止迭代并给出anchors
#loss_thresh为前后两次计算的loss的差值的一个阈值,两次loss的差值小于这个数,则停止迭代并给出anchors
#use_kmeans_plus_plus为种子点初始化时,是否使用kmean++方法来初始化种子点,1为使用,0为不使用
def cal_anchors(label_path,num_anchor,img_size_w, img_size_h,num_iterations,loss_thresh = 1e-3,use_kmeans_plusplus = 1):
    files = os.listdir(label_path)
    boxes = []
    for file in files:
        if not file == 'train.txt':
            txt_file = open(label_path + '/%s'%file,'r')
            list_txt_content = txt_file.read().split(' ')

            list_index = 0
            while(list_index < (len(list_txt_content) - 1)):
                list_index += 1
                x = float(list_txt_content[list_index].replace('\n',''))
                list_index += 1
                y = float(list_txt_content[list_index].replace('\n',''))
                list_index += 1
                w = float(list_txt_content[list_index].replace('\n',''))
                list_index += 1
                h = float(list_txt_content[list_index].replace('\n',''))
                boxes.append(Box(x,y,w,h))
            txt_file.close()

    if use_kmeans_plusplus:
        centroids = init_centroids(boxes,num_anchor)
    else:
        centroid_indices = np.random.choice(len(boxes),num_anchor)
        centroids = []
        for centroid_index in centroid_indices:
            centroids.append(boxes[centroid_index])

    centroids, groups, old_loss = do_kmeans(num_anchor,boxes,centroids)
    iteration = 1
    while (True):
        centroids, groups, loss = do_kmeans(num_anchor,boxes,centroids)
        iteration += 1
        #print('loss = %f' %loss)
        if abs(old_loss - loss) < loss_thresh or iteration > num_iterations:
            break
        old_loss = loss

    sum_iou = 0
    for i in range(num_anchor):
        for box in groups[i]:
            iou = cal_iou(box,centroids[i])
            sum_iou += iou
    avg_iou = sum_iou / len(boxes)

    centroids = sort_centroids(centroids)

    anchor_txt = open('./anchor.txt','w')
    for centroid in centroids:
        anchor = ' %(width)3.0f,%(height)3.0f,'%{'width':(centroid.w * img_size_w), 'height':(centroid.h * img_size_h)}
        print(anchor,end='')
        anchor_txt.write(anchor)
    print()


    anchor_txt.close()
    print('The Accuracy(average IOU) is ',avg_iou)

if __name__ == '__main__':
    # 由标注的文件生成的txt文件所在的路径
    label_path = '/home/xxxx/work/xj/datasets/water/dataparse_water/WaterMeterData/labels'
    # #生成的anchors的数量
    num_anchor = 6
    # #对应网络的size
    img_size_w = 256
    img_size_h = 256
    # #计算anchor时的最大迭代次数
    num_iterations = 2000
    # #计算anchors,并在脚本同目录下生一个anchor.txt文件来记录anchors的数值
    cal_anchors(label_path,num_anchor,img_size_w, img_size_h,num_iterations)



使用darknet训练模型

注意事项:

  • .data文件。该文件包含一些配置信息,具体为训练的总类别数,训练数据和验证数据的路径,类别名称,模型存放路径等。
  • 在读入训练数据时,只给程序输入了图片所在路径,而标签数据的路径并没有直接给,是通过对图片路径进行修改得到的。(替换的前提是,标签数据文件夹labels与图片数据文件夹images具有相同的父目录。另外,直接把txt标签文件放在与图片同一路径下也没问题。)
  • .cfg文件。主要包含训练的一些配置信息,如输入图像大小、学习率、数据增强等。还包括训练的网络结结构。
    详见:注意事项详细说明

模型训练命令

./darknet detector test <data_cfg> <models_cfg> <weights> <test_file> [-thresh] [-out]
./darknet detector train <data_cfg> <models_cfg> <weights> [-thresh] [-gpu] [-gpus] [-clear]
./darknet detector valid <data_cfg> <models_cfg> <weights> [-out] [-thresh]
./darknet detector recall <data_cfg> <models_cfg> <weights> [-thresh]

'< >'必选项,’[ ]‘可选项

  • data_cfg:数据配置文件,eg:cfg/voc.data
  • models_cfg:模型配置文件,eg:cfg/yolov3-voc.cfg
  • weights:权重配置文件,eg:weights/yolov3.weights
  • test_file:测试文件,eg://*/test.txt
  • -thresh:显示被检测物体中confidence大于等于 [-thresh] 的bounding-box,默认0.005
  • -out:输出文件名称,默认路径为results文件夹下,eg:-out “” //输出class_num个文件,文件名为class_name.txt;若不选择此选项,则默认输出文件名为comp4_det_test_“class_name”.txt
  • -i/-gpu:指定单个gpu,默认为0,eg:-gpu 2
  • -gpus:指定多个gpu,默认为0,eg:-gpus 0,1,2

训练模型并保存日志(保存至当前文件下的log.txt文件中):

./darknet detector train cfg/voc.data cfg/yolov3-tiny.cfg backup202106032111/yolov3-tiny.backup > results/log.txt

训练模型可视化

yolov3训练日志可视化主要为loss和iou曲线的可视化,这些是我们查看训练效果的重要依据。

其中,darknet训练时控制台打印的log信息截图:
在这里插入图片描述
log信息解析:

  • Aug IOU:当前迭代中,预测的box与标注的box的平均交并比,越大越好,期望数值为1;
  • Class:标注物体的分类准确率,越大越好,期望数值为1;
  • obj: 越大越好,期望数值为1;
  • No obj: 越小越好;
  • .5R: 以IOU=0.5为阈值时候的recall; recall = 检出的正样本/实际的正样本
  • 0.75R: 以IOU=0.75为阈值时候的recall;
  • count:正样本数目。
    注:存在nan值说明该子批次没有预测到正样本,在训练开始时候有出现是正常现象。

每批次在以后一行输出意义:

  • 第几批次:总损失,平均损失,当前学习率,当前批次训练时间,目前为止参与训练的图片总数

模型训练可视化脚本:

#coding=utf-8
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(levelname)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

class Yolov3LogVisualization:

    def __init__(self,log_path,result_dir):

        self.log_path = log_path
        self.result_dir = result_dir

    def extract_log(self, save_log_path, key_word):
        with open(self.log_path, 'r') as f:
            with open(save_log_path, 'w') as train_log:
                next_skip = False
                for line in f:
                    if next_skip:
                        next_skip = False
                        continue
                    # 去除多gpu的同步log
                    if 'Syncing' in line:
                        continue
                    # 去除除零错误的log
                    if 'nan' in line:
                        continue
                    if 'Saving weights to' in line:
                        next_skip = True
                        continue
                    if key_word in line:
                        train_log.write(line)
        f.close()
        train_log.close()

    def parse_loss_log(self,log_path, line_num=2000):
        result = pd.read_csv(log_path, skiprows=[x for x in range(line_num) if ((x % 10 != 9) | (x < 1000))],error_bad_lines=False, names=['loss', 'avg', 'rate', 'seconds', 'images'])
        result['loss'] = result['loss'].str.split(' ').str.get(1)
        result['avg'] = result['avg'].str.split(' ').str.get(1)
        result['rate'] = result['rate'].str.split(' ').str.get(1)
        result['seconds'] = result['seconds'].str.split(' ').str.get(1)
        result['images'] = result['images'].str.split(' ').str.get(1)

        result['loss'] = pd.to_numeric(result['loss'])
        result['avg'] = pd.to_numeric(result['avg'])
        result['rate'] = pd.to_numeric(result['rate'])
        result['seconds'] = pd.to_numeric(result['seconds'])
        result['images'] = pd.to_numeric(result['images'])
        return result

    def gene_loss_pic(self, pd_loss):
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
        ax.plot(pd_loss['avg'].values, label='avg_loss')
        ax.legend(loc='best')
        ax.set_title('The loss curves')
        ax.set_xlabel('batches')
        fig.savefig(self.result_dir + '/avg_loss')
        logger.info('save iou loss done')

    def loss_pic(self):
        train_log_loss_path = os.path.join(self.result_dir, 'train_log_loss.txt')
        self.extract_log(train_log_loss_path, 'images')
        pd_loss = self.parse_loss_log(train_log_loss_path)
        self.gene_loss_pic(pd_loss)


    def parse_iou_log(self,log_path, line_num=2000):
        result = pd.read_csv(log_path, skiprows=[x for x in range(line_num) if (x % 10 == 0 or x % 10 == 9)],error_bad_lines=False,names=['Region Avg IOU', 'Class', 'Obj', 'No Obj', 'Avg Recall', 'count'])
        result['Region Avg IOU'] = result['Region Avg IOU'].str.split(': ').str.get(1)
        result['Class'] = result['Class'].str.split(': ').str.get(1)
        result['Obj'] = result['Obj'].str.split(': ').str.get(1)
        result['No Obj'] = result['No Obj'].str.split(': ').str.get(1)
        result['Avg Recall'] = result['Avg Recall'].str.split(': ').str.get(1)
        result['count'] = result['count'].str.split(': ').str.get(1)

        result['Region Avg IOU'] = pd.to_numeric(result['Region Avg IOU'])
        result['Class'] = pd.to_numeric(result['Class'])
        result['Obj'] = pd.to_numeric(result['Obj'])
        result['No Obj'] = pd.to_numeric(result['No Obj'])
        result['Avg Recall'] = pd.to_numeric(result['Avg Recall'])
        result['count'] = pd.to_numeric(result['count'])
        return result

    def gene_iou_pic(self, pd_loss):
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
        ax.plot(pd_loss['Region Avg IOU'].values, label='Region Avg IOU')
        # ax.plot(result['Class'].values,label='Class')
        # ax.plot(result['Obj'].values,label='Obj')
        # ax.plot(result['No Obj'].values,label='No Obj')
        # ax.plot(result['Avg Recall'].values,label='Avg Recall')
        # ax.plot(result['count'].values,label='count')
        ax.legend(loc='best')
        ax.set_title('The Region Avg IOU curves')
        ax.set_xlabel('batches')
        fig.savefig(self.result_dir + '/region_avg_iou')
        logger.info('save iou pic done')

    def iou_pic(self):
        train_log_loss_path = os.path.join(self.result_dir, 'train_log_iou.txt')
        self.extract_log(train_log_loss_path, 'IOU')
        pd_loss = self.parse_iou_log(train_log_loss_path)
        self.gene_iou_pic(pd_loss)


if __name__ == '__main__':
    log_path = '/path/to/log/file' # 模型训练输出日志 可在训练命令后重定向获得
    result_dir = '/path/to/save/result' # 分析的结果文件输出所在文件夹
    logVis = Yolov3LogVisualization(log_path,result_dir)
    logVis.loss_pic()
    logVis.iou_pic()

使用训练好的模型进行预测

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值