上篇blog说了数据集制作的问题,然后?就开始train了。
一 参数含义
先理解下output的参数代表的含义吧。随便截了一个输出
每一个batch都会输出一行信息。
6274:迭代次数
0.479518:当前batch的loss
0.625265 :avg loss,是平均Loss,这个数值应该越低越好,一般来说,一旦这个数值低于0.060730 avg就可以终止训练了。
rate 代表当前的学习率,是在.cfg文件中定义的。
seconds: 表示当前批次训练花费的总时间。
images: 这一行最后的这个数值是6274*64的大小,表示到目前为止,参与训练的图片的总量。
Region Avg IOU: 表示在当前subdivision内的图片的平均IOU,代表预测的矩形框和真实目标的交集与并集之比.
Class: 标注物体分类的正确率,期望该值趋近于1。
Obj: 越接近1越好。
No Obj: 期望该值越来越小,但不为零。
count: count后的值是所有的当前subdivision图片中包含正样本的图片的数量。
后面显示了所有训练图片的一个批次(batch),批次大小的划分根据我们在 .cfg 文件中设置的subdivisions参数。在我使用的 .cfg 文件中 batch = 64 ,subdivision = 16,所以在训练输出中,训练迭代包含了16组,每组又包含了4张图片,跟设定的batch和subdivision的值一致。
(注: 也就是说每轮迭代会从所有训练集里随机抽取 batch = 64 个样本参与训练,所有这些 batch 个样本又被均分为 subdivision = 16次送入网络参与训练,以减轻内存占用的压力)
二 train bug
第一个让我无奈的bug。。刚开始train什么都没干,过个几秒就出来了。
Segmentation fault(core dump)
(下述core dump的含义:操作系统把程序当掉时的内存内容 dump 出来(现在通常是写在一个叫 core 的 file 里面),让我们或是 debugger 做为参考。这个动作就叫作 core dump。)
懵逼半天,retry了n次 都是基本2-3分钟卡断一次,没办法只好找bug啦。关键还没有人遇到这个,因为段错误范围太大了,没报错具体信息,只好先找找能不能找到错误log。
使用core与gdb进行调试,先让系统生成core文件啦,然后加载gdb:
gdb ./darknet ./core
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./darknet detector train cfg/coco.data cfg/yolov3.cfg darknet53.conv.74 -gpus 0'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000000000048f6b5 in get_yolo_box (x=0x412026c0, biases=0xc3bd70, n=8, index=1645163760,
i=15999984, j=15999984, lw=16, lh=16, w=512, h=512, stride=256) at ./src/yolo_layer.c:86
86 b.x = (i + x[index + 0*stride]) / lw;
[Current thread is 1 (Thread 0x7f0695be7700 (LWP 23910))]
我就看到了这些,一看这么大的下标肯定是哪里出问题了,可能是divide zero了。 在github上darknet的issue上找这个问题,有个老哥给了一个方案,查看labels中是否存在有zero值。
然后无奈。因为我的json文件转换的代码是不可能出现0值的,于是就去迭代找bbox中是否有空值。妈耶果不其然:
有16张图片bbox都为0,我去查看了对应的jpg,是背景图,进行了标注但是没有任何bbox的信息,yolov3的训练是不支持这样的图片的。
剔除了这16张图片对应的jpg和lables信息,重新生成了train.txt文件后,就可以正常训练了。
三 训练过程可视化
yolo会输出中间训练过程的值,但是我们想得到更全面的信息比如loss跟iou曲线的话,需要写一些脚本进行可视化。
参考blog 首先是在训练的过程的命令 要将训练输出的内容重定向到一个文件中
./darknet detector train cfg/coco.data cfg/yolov3.cfg backup/yolov3.backup -gpus 0,1,2,3 2>1 | tee -a train.txt
注意输出的内容在stderr中,重定向中需要注意。之后就生成了对应的train的log文件。我是从5000次开始保存开启了多尺度训练,到50000次的时候大概有250M的log。文件,然后通过下面的代码去除nan值生成对应的loss.txt跟Iou.txt
#!/usr/bin/python
# coding=utf-8
# 该文件用于提取训练log,去除不可解析的log后使log文件格式化,生成新的log文件供可视化工具绘图
import inspect
import os
import random
import sys
def extract_log(log_file, new_log_file, key_word):
with open(log_file, 'r') as f:
with open(new_log_file, 'w') as train_log:
for line in f:
# 去除多GPU的同步log;去除除零错误的log
if ('Syncing' in line) or ('nan' in line):
continue
if key_word in line:
train_log.write(line)
f.close()
train_log.close()
extract_log('/Users/zhangzhenghao/Desktop/train_yolov3.txt', '/Users/zhangzhenghao/Desktop/log_loss.txt', 'images')
extract_log('/Users/zhangzhenghao/Desktop/train_yolov3.txt', '/Users/zhangzhenghao/Desktop/log_iou.txt', 'IOU')
下图对应自己的log文件所在location,然后再通过下面两个代码进行图的绘制。
loss:
#!/usr/bin/python
# coding=utf-8
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 根据自己的log_loss.txt中的行数修改lines, 修改训练时的迭代起始次数(start_ite)和结束次数(end_ite)。
lines = 4500
start_ite = 5201 # log_loss.txt里面的最小迭代次数
end_ite = 50200 # log_loss.txt里面的最大迭代次数
step = 10 # 跳行数,决定画图的稠密程度
igore = 0 # 当开始的loss较大时,你需要忽略前igore次迭代,注意这里是迭代次数
y_ticks = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1] # 纵坐标的值,可以自己设置。
data_path = '/Users/zhangzhenghao/Desktop/log_loss.txt' # log_loss的路径。
result_path = '/Users/zhangzhenghao/Desktop/avg_loss' # 保存结果的路径。
####-----------------只需要改上面的,下面的可以不改动
names = ['loss', 'avg', 'rate', 'seconds', 'images']
result = pd.read_csv(data_path, skiprows=[x for x in range(lines) if
(x < lines * 1.0 / ((end_ite - start_ite) * 1.0) * igore or x % step != 9)],
error_bad_lines= \
False, names=names)
result.head()
for name in names:
result[name] = result[name].str.split(' ').str.get(1)
result.head()
result.tail()
for name in names:
result[name] = pd.to_numeric(result[name])
result.dtypes
print(result['avg'].values)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
###-----------设置横坐标的值。
x_num = len(result['avg'].values)
tmp = (end_ite - start_ite - igore) / (x_num * 1.0)
x = []
for i in range(x_num):
x.append(i * tmp + start_ite + igore)
# print(x)
print('total = %d\n' % x_num)
print('start = %d, end = %d\n' % (x[0], x[-1]))
###----------
ax.plot(x, result['avg'].values, label='avg_loss')
# ax.plot(result['loss'].values, label='loss')
plt.yticks(y_ticks) # 如果不想自己设置纵坐标,可以注释掉。
plt.grid()
ax.legend(loc='best')
ax.set_title('The loss curves')
ax.set_xlabel('batches')
fig.savefig(result_path)
# fig.savefig('loss')
IOU:
#!/usr/bin/python
# coding=utf-8
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 根据log_iou修改行数
lines = 1736397
step = 5000
start_ite = 0
end_ite = 300000
igore = 1000
data_path = '/Users/zhangzhenghao/Desktop/log_iou.txt' # log_loss的路径。
result_path = '/Users/zhangzhenghao/Desktop/Region_Avg_IOU' # 保存结果的路径。
names = ['Region Avg IOU', 'Class', 'Obj', 'No Obj', '.5_Recall', '.7_Recall', 'count']
# result = pd.read_csv('log_iou.txt', skiprows=[x for x in range(lines) if (x%10==0 or x%10==9)]\
result = pd.read_csv(data_path, skiprows=[x for x in range(lines) if
(x < lines * 1.0 / ((end_ite - start_ite) * 1.0) * igore or x % step != 0)] \
, error_bad_lines=False, names=names)
result.head()
for name in names:
result[name] = result[name].str.split(': ').str.get(1)
result.head()
result.tail()
for name in names:
result[name] = pd.to_numeric(result[name])
result.dtypes
####--------------
x_num = len(result['Region Avg IOU'].values)
tmp = (end_ite - start_ite - igore) / (x_num * 1.0)
x = []
for i in range(x_num):
x.append(i * tmp + start_ite + igore)
# print(x)
print('total = %d\n' % x_num)
print('start = %d, end = %d\n' % (x[0], x[-1]))
####-------------
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, result['Region Avg IOU'].values, label='Region Avg IOU')
# ax.plot(result['Avg Recall'].values, label='Avg Recall')
plt.grid()
ax.legend(loc='best')
ax.set_title('The Region Avg IOU curves')
ax.set_xlabel('batches')
fig.savefig(result_path)
对应的结果:我的是从5000次到5w次的迭代:
四 关于模型的保存
源码中:
迭代次数小于1000时,每100次保存一次,大于1000时,每10000次保存一次。
自己可以根据需求进行更改,然后重新编译即可[ 先 make clean ,然后再 make]。
代码位置: examples/detector.c line 138
这里如果迭代次数多的话建议大家5000次保存一次result,我就出现了4万次迭代的mAP比5万次的好,中间是不是有更好的就不清楚了。