如同第一个单元所述,这个项目的第三大块就是训练模型的环节了,是整个项目的最终临门一脚。在 “图片数据集” 和 “数据标签文档” 备齐的情况下,剩下的工作就是把这些产生的文档交给电脑做后续处理,使用的现成 YOLO3 神经网络架构并不在我们修改范围,创建神经网络的复杂工作也不在此篇介绍范围,这次的重点是如何使用我们自己标定的数据完成迁移学习的过程,并得出理想结果。
我的理解中,迁移学习就是一个站在巨人肩膀上看世界的一系列过程,通常迁移学习在开始之前,会有一个已经建构好的模型,与经过 “巨人们” 训练好的权重 weight 参数集,很多时候作为一个小小个体户,能够生成与创造的数据集总是没办法有多完备,甚至身边的硬件也没办法支持我们开展复杂的神经网络训练过程,这时候拿别人训练好并公开的 weight 参数直接使用,可以说是一件畅快至极的一件事呢!
但是别人的训练内容与导向,不可能完美的与我们的目标一致,总是需要一些修改,而这也是迁移学习中我们主要的着眼点。是否能找到与自己目标相近的 “巨人” 公开的权重参数集也成了另一个刚性约束。
这次的 YOLO 迁移学习中,我们只对最后一层的神经网络进行修改,并且基于现有的权重继续开始我们新提供的数据集训练。原本在完整训练神经网络的最初,需要初始化的部分就像现在直接套上权重的过程,换言之,迁移学习里旧的权重就是我们初始化训练的起点,不用重新从一个复杂的方程截面开始梯度下降,相较于重新开始的完整训练,迁移学习大幅节省时间的情况下,也能达到类似优异的效果!
它是一个基于 C 语言编写的神经网络,并且被打包到 darknet [点击下载] 中(官网提供下载网址),而与 python 串接的方式则是借由 darkflow [点击下载安装] 模块来完成,它有自己的指令调用 darknet 中神经网络架构文档,载入神经网络权重文件,并最后回传神经网络的计算结果,是一个完整方便且思路清晰的 AI 模块。
Terminal 里面的执行开始训练的代码如下:
sudo flow --model cfg/tiny-yolo-voc-4c.cfg --load bin/tiny-yolo-voc.weights
--train --annotation teeth_brush_xml --dataset teeth_brush --epoch 800
其中细节包含了两个文件的修改:目标 .cfg 文件,与 darkflow 之中 label.txt 文件。
事件 1: .cfg 文件修改
把文件用文字编译器打开后,就会发现它是一个整齐化一,且针对每层神经网络参数描述详细的文档,拉到最下面一层神经网络的显示内容后,把要训练的物体种类数量在 [region] 所属的下面为止打上,并在 filter = ... 的位置输入上 5*(class + 5) 的计算结果,完事之后保存。并最后把档名些微修改成我们修改过的样子以防搞混。
事件 2: label.txt 修改标签名称
打开 darknet 里面的该文档,把自己要取的种类名称输入上存档即可。
接下来开启 CMD 或是 Terminal 执行这些放在不同地方的文档了,记得执行的位置要先用 cd ... 去到 darknet 的文件夹后,再开始执行才会有效!
训练过程中,一个名为 ckpt 的资料夹会自动生成在该执行目录下,里面的文件会在每次循环训练次数达到我们设定的 filter 数量整数倍时自动生成,它就像是一个走过留下痕迹的中间存档,当然也可以最后呼叫中间存档下来的 weights 文件,但是效果肯定不会比训练时间更长的文件好。
使用到的 python modules:
# 内置 python 时间模块,这里准备用来计算每一帧视频的耗费
import time
# 用来调用摄像头与图形相关的函数功能
import cv2
import numpy as np
# 整个神经网络建构于此模块中,用以导出最终计算结果
from darkflow.net.build import TFNet
# 开启一个 camera,编号从 0 开始一路往上加 1, 2, 3...(如果电脑连接那么多摄像头的话)
camera = cv2.VideoCapture(0)
# 调整跳出视窗的 “横宽” 和 “竖高” 的大小
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
# 设定神经网络的参数,在 model 要使用什么模型,load 要加载什么权重文件
# threshold 影响神经网络判断是非的阈值,太高的话什么都辨识不出来,太低的话看到什么都觉得是标签的一种
# gpu 开启与否,数字决定开启的程度,1 表示全开,如果要留 gpu 在训练的时候预留做其他功能,可以调成 0.?
condition = {
'model': 'cfg/tiny-yolo-voc-4c.cfg',
'load': 8000,
'threshold': 0.05,
'gpu': 1.0
}
# 把上面设定好的 condition 放入 darkflow 模块里面
net = TFNet(condition)
# 设定标记框框的颜色种类,给几组颜色不重要,希望就是能比预测出来的标签数据多
# 这样后面 zip 起来的结果才不会有些检测到的标签没有颜色可以显示
colors = [tuple(255 * np.random.rand(3) for i in range(10)]
# 是一个 opencv 实时播放 camera 画面的方式,不断的回圈中在每个不同微小差异的时间段下
# 显示当时 camera 抓取的瞬时内容并及时打印,这个过程一旦快起来,就会认为是一段影片
while True:
# 按下码表开始计算每一次画面刷新所需时间
star_t = time.time()
# 回传一个 Bool 和跟 imread 得到的同种 object
success, frame = camera.read()
# 如果回传回来是 True
if success:
# 把该 frame 代入神经网络做预测得到的结果赋值给 results(是一个 list 装满了 dict 的格式)
results = net.return_predict(frame)
# 把结果和 colors 结合在一起,谁比较多的部分就会因为 zip 砍掉不要了,所以 colors 要够多才行
for color, result in zip(colors, results):
LABEL = result['label']
CONF = result['confidence']
TLC = (result['topleft']['x'], result['topleft']['y'])
BRC = (result['bottcomright']['x'], result['bottomright']['y'])
note = '{}: {:.0f}%'.format(LABEL, CONF * 100)
# 在画面上放上矩形方框与文字标注名称和可能性
frame = cv2.rectangle(frame, TLC, BRC, color, 5)
frame = cv2.putText(frame, note, TLC, cv2.FONT_HERSHEY_COMPLEX, 1, (0, 0, 0), 2)
# 打印每次 camera 得到画面,并加以预测得出结果,然后画上框框和文字信息的图像内容,命名为 'personal tags'
cv2.imshow('personal tags', frame)
# 在 python 中计算每一次所需时间,并打印出来参考
print('FPS {:.1f}'.format(1 / (time.time() - star_t)))
# 如果要关掉程序,按下键盘 'q' 键即可
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 原本呼叫的 VideoCapture 需要在这里 release 确保摄像头关闭
camera.release()
# 把所有的弹出来的视窗全部清除
cv2.destroyAllWindows()
使用别人模型展开训练是一个捷径,使用别人训练好的权重文档加以修改后,成为自己的一部分是一种快感!但是必须永远切记机器学习的要点,训练集表现不论多好,从实用意义的角度来看,永远不是我们关注的焦点,我们注重的是面对未知的数据情况下,其表现优异程度如何。
牙刷辨认系统的结果如下图:
由于训练期间没有 GPU 辅助,三百张图片做为训练集的情况下已经让训练时长到了 4 天之久,不得已的情况下,在 loss value 还在 2.x 震荡的时候就叫停了训练,直接用训练到一半的权重文档来检视结果,因此得出了低的准确率。
剩下的工作就是把数据和对应的标注文档放入神经网络模型中使其顺利训练,最后使用该训练好的结果检视从不论是新的图片还是摄像头捕捉到的画面里面的内容物。
下一步朝着神经网络细节继续迈进!